{"openapi":"3.1.0","info":{"title":"GetTorrent v2 API","description":"Modular monolith backing both gettorrent and get2cloud skins.","license":{"name":"Proprietary"},"version":"v1"},"servers":[{"url":"https://api.dev.gettorrent.cloud","description":"dev"},{"url":"http://localhost:8080","description":"local"}],"security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"tags":[{"name":"analytics","description":"Browser analytics beacons."},{"name":"billing","description":"Subscription, payment history, cancellation."},{"name":"health","description":"Liveness and build metadata."},{"name":"admin","description":"Administrative endpoints — admin realm role required."},{"name":"automation","description":"RSS watch, completion reactions and retention rules."},{"name":"metadata","description":"Catalogue metadata (posters, titles) for torrents."},{"name":"plans","description":"Public list of purchasable plan tiers."},{"name":"accounts","description":"Account lookup, profile update, API key, and password change."},{"name":"support","description":"Contact the support team."},{"name":"streaming","description":"Per-torrent access checks consumed by gettorrent-v2-streamer."},{"name":"admin","description":"Administration"},{"name":"categories","description":"Download categories: the taxonomy and the owner's choice per torrent."},{"name":"torrents","description":"List, add, inspect, and delete user-owned torrents."},{"name":"works","description":"Identified works and their cards: description, poster, trailer."},{"name":"uploads","description":"Uploads of the owner's own files (tus, through the files service)."},{"name":"tv","description":"TV / QR device login — create, poll, approve, exchange."},{"name":"watch","description":"Playback positions behind continue watching."}],"paths":{"/api/v1/watch/progress":{"get":{"tags":["watch"],"summary":"Playback positions of every file of a torrent","operationId":"forTorrent","parameters":[{"name":"torrent_id","in":"query","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProgressListResponse"}}}}}},"put":{"tags":["watch"],"summary":"Save the playback position of one file","operationId":"save","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveProgressRequest"}}},"required":true},"responses":{"200":{"description":"OK"}}}},"/api/v1/watch/preferences":{"get":{"tags":["watch"],"summary":"The player choices that follow the account: audio language, subtitle choice","operationId":"preferences","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PreferencesResponse"}}}}}},"put":{"tags":["watch"],"summary":"Remember a choice made in the player; an absent field stays as it is","operationId":"savePreferences","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreferencesRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PreferencesResponse"}}}}}}},"/api/v1/torrents/{id}/work/poster":{"put":{"tags":["works"],"summary":"Choose the work's poster among the offered pictures","description":"The poster is the work's: everyone who has a download of it sees the new one. 409 when the name was not offered for this work (ask for the choices again).","operationId":"choosePoster","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PosterChoiceRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/WorkResponse"}}}}}}},"/api/v1/torrents/{id}/tags":{"put":{"tags":["torrents"],"summary":"Replace the tag list of a torrent","description":"Full-document PUT. Tags are trimmed, de-duplicated case-insensitively, max 20 per torrent, 32 chars each.","operationId":"setTags","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetTagsRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TorrentResponse"}}}}}}},"/api/v1/torrents/{id}/metadata":{"get":{"tags":["metadata"],"summary":"Resolved metadata of a torrent","operationId":"get","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/MetadataSummary"}}}}}},"put":{"tags":["metadata"],"summary":"Pick the catalogue entry for a torrent (manual override)","operationId":"override","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OverrideRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/MetadataSummary"}}}}}},"delete":{"tags":["metadata"],"summary":"Declare the torrent has no catalogue entry (hides the poster)","operationId":"miss","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/MetadataSummary"}}}}}}},"/api/v1/torrents/{id}/category":{"get":{"tags":["categories"],"summary":"The torrent's category","description":"Null category = without category; pending while the classifier works.","operationId":"get_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CategoryResponse"}}}}}},"put":{"tags":["categories"],"summary":"Set the torrent's category","description":"The owner's choice is final: the automatic classifier never changes it afterwards.","operationId":"set","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CategoryResponse"}}}}}}},"/api/v1/billing/autopay":{"get":{"tags":["Billing"],"summary":"Autopay state and saved cards","operationId":"status","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AutopayResponse"}}}}}},"put":{"tags":["Billing"],"summary":"Switch autopay on (with a saved card) or off","operationId":"update","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutopayUpdateRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AutopayResponse"}}}}}}},"/api/v1/admin/users/{id}/plan":{"put":{"tags":["admin"],"summary":"Assign a plan to a user","description":"No payment is involved and autopay never renews the grant. With ends_at the user returns to Free after that time; without it the plan stays until changed. plan_code=free ends the current plan immediately. Archived plans are refused.","operationId":"assignPlan","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminAssignPlanRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminUserPlanResponse"}}}}}}},"/api/v1/admin/plans/{id}":{"put":{"tags":["admin"],"summary":"Edit a plan tier","description":"Full-document replace; the plan code is immutable.","operationId":"update_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanUpsertRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PlanResponse"}}}}}},"delete":{"tags":["admin"],"summary":"Archive a plan tier","description":"Soft-delete: the tier is hidden from the public list but never hard-deleted (a subscriber or a past payment may still reference its code).","operationId":"archive","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PlanResponse"}}}}}}},"/api/v1/accounts/me/settings":{"get":{"tags":["accounts"],"summary":"Get the current account's self-service settings","description":"The download-complete email toggle (default ON) and the 18+ visibility (shown by default).","operationId":"getSettings","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AccountSettingsResponse"}}}}}},"put":{"tags":["accounts"],"summary":"Update the current account's self-service settings","description":"Persists the preferences and returns the updated settings; absent optional fields stay as they are.","operationId":"updateSettings","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountSettingsRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AccountSettingsResponse"}}}}}}},"/api/v1/uploads/token":{"post":{"tags":["uploads"],"summary":"Where to upload a file, and a token for it","description":"The tus endpoint of the files service and a token to send as 'Authorization: Bearer' with every tus request. 404 when uploads are not configured.","operationId":"token","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/UploadTokenResponse"}}}}}}},"/api/v1/tv/sessions":{"post":{"tags":["tv"],"summary":"Create a pending TV-login session","description":"Public. Returns the session_id (held by the TV), a short user_code, the scan path for the QR, and the TTL in seconds.","operationId":"createSession","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CreateTvSessionResponse"}}}}}}},"/api/v1/tv/sessions/{session_id}/approve":{"post":{"tags":["tv"],"summary":"Approve a TV-login session (phone, by session_id)","description":"Authenticated. Binds the current user + mints the single-use exchange_code (revealed to the TV via poll, never returned here).","operationId":"approveBySessionId","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TvApproveResponse"}}}}}}},"/api/v1/tv/exchange":{"post":{"tags":["tv"],"summary":"Exchange the approved code for tokens","description":"Public. Validates the single-use code and mints a real Keycloak access+refresh token pair for the bound user. Marks the session consumed.","operationId":"exchange","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TvExchangeRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TvExchangeResponse"}}}}}}},"/api/v1/tv/approve":{"post":{"tags":["tv"],"summary":"Approve a TV-login session (phone, by user_code)","description":"Authenticated. Manual-entry alternative to scanning the QR.","operationId":"approveByCode","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TvApproveByCodeRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TvApproveResponse"}}}}}}},"/api/v1/torrents":{"get":{"tags":["torrents"],"summary":"List the user's torrents","description":"Cursor pagination; newest first.","operationId":"list","parameters":[{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int32"}},{"name":"tag","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ListTorrentsResponse"}}}}}},"post":{"tags":["torrents"],"summary":"Add a torrent from magnet or URL","description":"Validates the source, parses the info_hash and persists a queued row; with Kafka the download reaches the torrent client asynchronously (torrent.enqueue.requested), without it inline.","operationId":"createFromUpload","requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}},"application/json":{"schema":{"$ref":"#/components/schemas/CreateTorrentRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TorrentResponse"}}}}}}},"/api/v1/torrents/{id}/retry":{"post":{"tags":["torrents"],"summary":"Retry an errored torrent","description":"Re-enqueues the stored magnet/URL and puts the torrent back to queued. 409 when the torrent is not errored or cannot be retried (moderation stop).","operationId":"retry","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TorrentResponse"}}}}}}},"/api/v1/torrents/{id}/resume":{"post":{"tags":["torrents"],"summary":"Resume a paused torrent","operationId":"resume","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TorrentResponse"}}}}}}},"/api/v1/torrents/{id}/pause":{"post":{"tags":["torrents"],"summary":"Pause a queued or downloading torrent","description":"Keeps the concurrency slot. 409 when the torrent is not pausable.","operationId":"pause","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TorrentResponse"}}}}}}},"/api/v1/torrents/{id}/metadata/requeue":{"post":{"tags":["metadata"],"summary":"Re-run the automatic lookup","operationId":"requeue","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/MetadataSummary"}}}}}}},"/api/v1/torrents/{id}/files/move":{"post":{"tags":["torrents"],"summary":"Rename or move a file or folder inside a completed torrent","operationId":"moveFile","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Move"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TorrentFileResponse"}}}}}}},"/api/v1/torrents/{id}/files/folders":{"post":{"tags":["torrents"],"summary":"Create a folder inside a completed torrent","operationId":"createFolder","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFolder"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TorrentFileResponse"}}}}}}},"/api/v1/torrents/{id}/download-zip":{"post":{"tags":["torrents"],"summary":"Request a multi-file ZIP build","description":"Returns 202 with a ticket; the ZIP is built off-thread.","operationId":"requestZip","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateZipRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ZipTicketResponse"}}}}}}},"/api/v1/torrents/download-zip":{"post":{"tags":["torrents"],"summary":"Request one ZIP of several downloads","description":"Each torrent becomes a folder of the archive. Returns 202 with a ticket; status and download use the same zip-tickets endpoints as the per-torrent ZIP. 404 if any torrent is not the caller's, 403 if any is blocked, 409 if any has not finished downloading.","operationId":"requestMultiZip","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMultiZipRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ZipTicketResponse"}}}}}}},"/api/v1/torrents/bulk":{"post":{"tags":["torrents"],"summary":"Delete, pause, resume or retry many torrents at once","description":"One result per id, in request order. The torrent client calls are queued, so the answer does not wait for them; a download that cannot take the action says why.","operationId":"bulk","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkActionRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/BulkActionResponse"}}}}}}},"/api/v1/support/tickets":{"get":{"tags":["support"],"summary":"My tickets, newest activity first","operationId":"mine","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TicketListResponse"}}}}}},"post":{"tags":["support"],"summary":"Open a ticket","description":"429 after 5 per hour.","operationId":"openMultipart","parameters":[{"name":"category","in":"query","required":false,"schema":{"type":"string"}},{"name":"subject","in":"query","required":true,"schema":{"type":"string"}},{"name":"message","in":"query","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"files":{"type":"array","items":{"type":"string","format":"binary"}}}}},"application/json":{"schema":{"$ref":"#/components/schemas/OpenTicketRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TicketDetailResponse"}}}}}}},"/api/v1/support/tickets/{ticketId}/reopen":{"post":{"tags":["support"],"summary":"Re-open my closed ticket","operationId":"reopen","parameters":[{"name":"ticketId","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TicketDetailResponse"}}}}}}},"/api/v1/support/tickets/{ticketId}/messages":{"post":{"tags":["support"],"summary":"Reply to my ticket (re-opens it when closed)","operationId":"replyMultipart","parameters":[{"name":"ticketId","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"message","in":"query","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"files":{"type":"array","items":{"type":"string","format":"binary"}}}}},"application/json":{"schema":{"$ref":"#/components/schemas/ReplyRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TicketDetailResponse"}}}}}}},"/api/v1/support/tickets/{ticketId}/close":{"post":{"tags":["support"],"summary":"Close my ticket","operationId":"close","parameters":[{"name":"ticketId","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TicketDetailResponse"}}}}}}},"/api/v1/support/contact":{"post":{"tags":["support"],"summary":"Anonymous contact form (public)","description":"No auth; 3 messages per 10 minutes per IP. Obvious spam is accepted (202) and discarded; suspects are flagged for the support team.","operationId":"contact","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactRequest"}}},"required":true},"responses":{"200":{"description":"OK"}}}},"/api/v1/events":{"post":{"tags":["analytics"],"summary":"Send analytics beacons","description":"Up to 50 events / 32 KiB per request; unknown event types are dropped. 204 on success, 413 over the caps, 429 when the client IP sends too many requests.","operationId":"collect","responses":{"200":{"description":"OK"}}}},"/api/v1/billing/resume":{"post":{"tags":["billing"],"summary":"Undo a scheduled cancellation","description":"Clears cancel_at on the active subscription; the plan keeps renewing.","operationId":"resume_1","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SubscriptionResponse"}}}}}}},"/api/v1/billing/plan-change":{"get":{"tags":["Billing"],"summary":"What choosing another plan means right now","description":"kind: same | new (full-price checkout) | upgrade (the price difference for the days left, charged now; the plan starts at once) | downgrade (starts at the period end) | to_free (the paid plan runs to the period end). Nothing is written.","operationId":"previewPlanChange","parameters":[{"name":"plan_id","in":"query","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"period","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PlanChangePreviewResponse"}}}}}},"post":{"tags":["Billing"],"summary":"Change the plan","description":"Upgrade inside a paid period: the prorated difference is charged to the saved card (status applied | declined | error | in_flight) or, without a card or with hosted=true, paid on the hosted page (status checkout). Downgrade and the free tier: status scheduled, the paid plan stays until the period end. 409 when the amount is no longer expected_amount_cents.","operationId":"changePlan","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanChangeRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PlanChangeResponse"}}}}}},"delete":{"tags":["Billing"],"summary":"Withdraw a scheduled downgrade or a scheduled move to the free tier","operationId":"withdrawPlanChange","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SubscriptionResponse"}}}}}}},"/api/v1/billing/checkout":{"post":{"tags":["Billing"],"summary":"Start a card checkout for a plan (hosted payment page)","operationId":"checkout","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CheckoutResponse"}}}}}}},"/api/v1/billing/checkout/callback":{"post":{"tags":["Billing"],"summary":"Payment provider callback","description":"Authenticated by the provider signature.","operationId":"callback","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object","additionalProperties":{}}}}}}}},"/api/v1/billing/cancel":{"post":{"tags":["billing"],"summary":"Cancel the subscription at the end of the paid period","description":"The plan stays until the paid period ends and the saved card is not charged again; then the account moves to the free tier.","operationId":"cancel","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelRequest"}}}},"responses":{"200":{"description":"OK"}}}},"/api/v1/automation/rules":{"get":{"tags":["automation"],"summary":"List my automation rules","operationId":"list_1","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RuleListResponse"}}}}}},"post":{"tags":["automation"],"summary":"Create a rule","operationId":"create","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRuleRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RuleResponse"}}}}}}},"/api/v1/automation/rules/{id}/run":{"post":{"tags":["automation"],"summary":"Run a feed or retention rule now","operationId":"run","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RunResponse"}}}}}}},"/api/v1/admin/users/{id}/unsuspend":{"post":{"tags":["admin"],"operationId":"unsuspend","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminUserResponse"}}}}}}},"/api/v1/admin/users/{id}/suspend":{"post":{"tags":["admin"],"summary":"Suspend a user","description":"Marks v2 row + disables in Keycloak + revokes all SSO sessions.","operationId":"suspend","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSuspendRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminUserResponse"}}}}}}},"/api/v1/admin/users/{id}/revoke-role":{"post":{"tags":["admin"],"operationId":"revokeRole","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminRoleRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminUserResponse"}}}}}}},"/api/v1/admin/users/{id}/reset-password":{"post":{"tags":["admin"],"summary":"Send the user a password-reset email","description":"Triggers Keycloak's UPDATE_PASSWORD execute-actions-email. The admin never handles the new password — Keycloak mails the user a one-time link.","operationId":"resetPassword","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK"}}}},"/api/v1/admin/users/{id}/impersonate":{"post":{"tags":["admin"],"summary":"Mint a short-lived token to impersonate the user","description":"5 min TTL. The minted token is logged via target_user_id only — never the token bytes.","operationId":"impersonate","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminImpersonateResponse"}}}}}}},"/api/v1/admin/users/{id}/grant-role":{"post":{"tags":["admin"],"operationId":"grantRole","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminRoleRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminUserResponse"}}}}}}},"/api/v1/admin/torrent-migrations":{"get":{"tags":["admin"],"summary":"List migrations","description":"Newest first; ?source=<node> narrows to one source node.","operationId":"list_2","parameters":[{"name":"source","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":200}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/MigrationResponse"}}}}}}},"post":{"tags":["admin"],"summary":"Move a torrent to another node","description":"Queues the move of a completed torrent (all its rows: the files are shared). 409 when it is still downloading or already moving.","operationId":"move","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MoveRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/MigrationResponse"}}}}}}},"/api/v1/admin/torrent-migrations/{id}/retry":{"post":{"tags":["admin"],"operationId":"retry_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/MigrationResponse"}}}}}}},"/api/v1/admin/torrent-migrations/{id}/cancel":{"post":{"tags":["admin"],"operationId":"cancel_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/MigrationResponse"}}}}}}},"/api/v1/admin/torrent-migrations/drain":{"post":{"tags":["admin"],"summary":"Move every completed torrent off a node","description":"Torrents still downloading are skipped (drain again once they complete). Put the node in maintenance first so nothing new lands on it.","operationId":"drain","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DrainRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/DrainResponse"}}}}}}},"/api/v1/admin/plans":{"get":{"tags":["admin"],"summary":"List all plans (incl. archived)","description":"Admin view — unlike GET /api/v1/plans this includes archived tiers.","operationId":"list_3","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PlanListResponse"}}}}}},"post":{"tags":["admin"],"summary":"Create a plan tier","operationId":"create_1","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanUpsertRequest"}}},"required":true},"responses":{"201":{"description":"Created","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PlanResponse"}}}}}}},"/api/v1/admin/nodes/{id}/soft-delete":{"post":{"tags":["admin"],"summary":"Soft-delete a node + start torrent re-assignment job","description":"Triggers an async job that re-points torrents.node_assignment to the target node.","operationId":"softDelete","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSoftDeleteNodeRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminNodeResponse"}}}}}}},"/api/v1/admin/nodes/{id}/restore":{"post":{"tags":["admin"],"summary":"Restore a soft-deleted node","description":"Revives the inventory row to active + resets the migration state. Torrents already moved to the target node are NOT pulled back.","operationId":"restore","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminNodeResponse"}}}}}}},"/api/v1/admin/nodes/{id}/maintenance":{"post":{"tags":["admin"],"summary":"Mark node as maintenance","description":"Drains new assignments; existing torrents on the node stay put until soft-delete.","operationId":"maintenance","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminNodeResponse"}}}}}}},"/api/v1/admin/nodes/{id}/active":{"post":{"tags":["admin"],"summary":"Restore node to active","description":"Inverse of maintenance.","operationId":"active","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminNodeResponse"}}}}}}},"/api/v1/admin/moderation/blocked/{torrentId}/review":{"post":{"tags":["admin"],"summary":"Flip the reviewed flag on a BLOCKED verdict (triage only — never un-blocks)","operationId":"review","parameters":[{"name":"torrentId","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewRequest"}}}},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ModerationVerdictSummary"}}}}}}},"/api/v1/admin/moderation/accounts/{id}/unblock":{"post":{"tags":["admin"],"summary":"Lift a content-policy block after review","operationId":"unblockAccount","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReasonRequest"}}}},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/BlockState"}}}}}}},"/api/v1/admin/moderation/accounts/{id}/block":{"post":{"tags":["admin"],"summary":"Block an account (content policy)","operationId":"blockAccount","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReasonRequest"}}}},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/BlockState"}}}}}}},"/api/v1/admin/jobs/{key}/run":{"post":{"tags":["admin"],"summary":"Run a maintenance job now","description":"202 with the claimed run; 409 if already running.","operationId":"run_1","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/JobRunResponse"}}}}}}},"/api/v1/accounts/me/sessions/revoke-all":{"post":{"tags":["accounts"],"summary":"Revoke every active SSO session for the current account","description":"Calls Keycloak admin /users/{id}/logout. Idempotent — 204 even if no sessions existed.","operationId":"revokeAllSessions","responses":{"200":{"description":"OK"}}}},"/api/v1/accounts/me/password/change":{"post":{"tags":["accounts"],"summary":"Change the current account's password","description":"Verifies current_password via ROPC then resets via Keycloak admin API.","operationId":"changePassword","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePasswordRequest"}}},"required":true},"responses":{"200":{"description":"OK"}}}},"/api/v1/accounts/me/api-key/rotate":{"post":{"tags":["accounts"],"summary":"Rotate the current account's API key","description":"Revokes any existing live key and returns the new cleartext ONCE.","operationId":"rotateApiKey","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/Rotated"}}}}}}},"/api/v1/automation/rules/{id}":{"get":{"tags":["automation"],"summary":"One rule","operationId":"get_2","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RuleResponse"}}}}}},"delete":{"tags":["automation"],"summary":"Delete a rule","operationId":"delete","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK"}}},"patch":{"tags":["automation"],"summary":"Update name, enabled flag or config","operationId":"update_2","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRuleRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/RuleResponse"}}}}}}},"/api/v1/admin/nodes/{id}":{"get":{"tags":["admin"],"operationId":"get_3","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminNodeResponse"}}}}}},"delete":{"tags":["admin"],"summary":"Hard-remove a soft-deleted node row","description":"Permanently drops the inventory row. Only a soft-deleted node can be removed.","operationId":"delete_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK"}}},"patch":{"tags":["admin"],"summary":"Change a node's settings","description":"max_fill_percent (1..100, v1 default 95): once its disk is this full the node takes no new download and no migrated torrent; placement balances over the nodes below their cap.","operationId":"update_3","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminNodeSettingsRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminNodeResponse"}}}}}}},"/api/v1/accounts/me":{"get":{"tags":["accounts"],"summary":"Get the currently authenticated account","description":"JIT-creates the account row from the JWT 'sub' claim on first call.","operationId":"me","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AccountResponse"}}}}}},"patch":{"tags":["accounts"],"summary":"Update the current account's profile","description":"Partial update — null fields are left untouched.","operationId":"update_4","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAccountRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AccountResponse"}}}}}}},"/api/v1/watch/continue":{"get":{"tags":["watch"],"summary":"Last played file of each torrent, newest first","operationId":"continueWatching","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":12}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProgressListResponse"}}}}}}},"/api/v1/tv/sessions/{session_id}":{"get":{"tags":["tv"],"summary":"Poll a TV-login session","description":"Public. Reveals the one-time exchange_code ONLY when status=approved.","operationId":"pollSession","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TvSessionStatusResponse"}}}}}}},"/api/v1/torrents/{id}":{"get":{"tags":["torrents"],"summary":"Get a single torrent","description":"404 when the torrent is missing or belongs to another account. State and progress_pct come from the row the status poller keeps current; live bytes and speed are on /{id}/progress and the event stream.","operationId":"get_4","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TorrentResponse"}}}}}},"delete":{"tags":["torrents"],"summary":"Soft-delete a torrent","description":"Sets soft_deleted_at and best-effort cancels the aria2 job. Idempotent.","operationId":"delete_2","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK"}}}},"/api/v1/torrents/{id}/work":{"get":{"tags":["works"],"summary":"The work the torrent was identified as, with its card","operationId":"work","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/WorkResponse"}}}}}}},"/api/v1/torrents/{id}/work/posters":{"get":{"tags":["works"],"summary":"Pictures the owner may choose the work's poster from","description":"Stored copies (paths on this API), the current poster first. Takes a few seconds: the pictures are found and copied now.","operationId":"posterChoices","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PosterChoices"}}}}}}},"/api/v1/torrents/{id}/state":{"get":{"tags":["torrents"],"summary":"Live state stream (Server-Sent Events)","description":"event: progress, data: JSON progress sample. Snapshot first, then live samples; ':hb' comment every 15 s. Reconnect with EventSource.","operationId":"state","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"text/event-stream":{"schema":{"$ref":"#/components/schemas/SseEmitter"}}}}}}},"/api/v1/torrents/{id}/progress":{"get":{"tags":["torrents"],"summary":"Live progress snapshot","description":"Backed by aria2.tellStatus, cached 2 s. Use for polling-based UIs.","operationId":"progress","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TorrentProgressResponse"}}}}}}},"/api/v1/torrents/{id}/files":{"get":{"tags":["torrents"],"summary":"List the torrent's files","description":"Walks the on-disk dir when completed, falls back to aria2's metadata otherwise.","operationId":"files","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TorrentFileResponse"}}}}}},"delete":{"tags":["torrents"],"summary":"Delete a file or folder inside a completed torrent","description":"Folders need recursive=true unless empty. The torrent itself is deleted via DELETE /{id}.","operationId":"deleteFile","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"path","in":"query","required":true,"schema":{"type":"string"}},{"name":"recursive","in":"query","required":false,"schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TorrentFileResponse"}}}}}}},"/api/v1/torrents/{id}/files/download":{"get":{"tags":["torrents"],"summary":"Download one file","description":"Streams one file of a completed torrent. Honours Range, so an interrupted download can resume.","operationId":"downloadFile","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"path","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"string","format":"binary"}}}}}}},"/api/v1/torrents/{id}/files/download-link":{"get":{"tags":["torrents"],"summary":"A signed link to download one file directly","description":"The file is served by the files service on the node that holds it (Range, resume), not through the API. 404 when the files service is not configured: use /files/download.","operationId":"downloadLink","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"path","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/DownloadLinkResponse"}}}}}}},"/api/v1/torrents/zip-tickets/{ticketId}":{"get":{"tags":["torrents"],"summary":"Get the state of a ZIP ticket","description":"Returns the ticket lifecycle (pending|ready|errored) and expires_at.","operationId":"zipTicketStatus","parameters":[{"name":"ticketId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ZipTicketResponse"}}}}}}},"/api/v1/torrents/zip-tickets/{ticketId}/download":{"get":{"tags":["torrents"],"summary":"Stream the built ZIP","description":"Single-use by default; marks the ticket consumed.","operationId":"downloadZip","parameters":[{"name":"ticketId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"string","format":"binary"}}}}}}},"/api/v1/torrents/usage":{"get":{"tags":["torrents"],"summary":"Storage / concurrency usage against the effective plan","description":"Feeds the client quota widget and the upgrade call-to-action.","operationId":"usage","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TorrentUsageResponse"}}}}}}},"/api/v1/torrents/tags":{"get":{"tags":["torrents"],"summary":"Distinct tags over the user's torrents with usage counts","operationId":"tags","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TagListResponse"}}}}}}},"/api/v1/torrents/events":{"get":{"tags":["torrents"],"summary":"Live state of every torrent of the account (Server-Sent Events)","description":"One connection for the whole library: a progress event per active torrent first, then every sample of any of the account's torrents (about every 2 s while the stream is open). ':hb' comment every 15 s. Reconnect with EventSource.","operationId":"events","responses":{"200":{"description":"OK","content":{"text/event-stream":{"schema":{"$ref":"#/components/schemas/SseEmitter"}}}}}}},"/api/v1/support/tickets/{ticketId}":{"get":{"tags":["support"],"summary":"One of my tickets with its public conversation","operationId":"get_5","parameters":[{"name":"ticketId","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TicketDetailResponse"}}}}}}},"/api/v1/support/tickets/{ticketId}/messages/{messageId}/attachments/{attachmentId}":{"get":{"tags":["support"],"summary":"Download a file attached to a message of my ticket","operationId":"attachment","parameters":[{"name":"ticketId","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"messageId","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"attachmentId","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"string","format":"byte"}}}}}}},"/api/v1/stream/{torrent_id}/can-play":{"get":{"tags":["streaming"],"summary":"Access check for the streamer","description":"Returns the info_hash + relative media path when the caller owns the torrent and it is in a streamable state. With `file`, selects one file of the torrent instead of its largest video.","operationId":"canPlay","parameters":[{"name":"torrent_id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"file","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/CanPlayResponse"}}}}}}},"/api/v1/public/work-images/{name}":{"get":{"tags":["works"],"summary":"A stored poster","description":"Immutable: a new picture gets a new name.","operationId":"image","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"string","format":"byte"}}}}}}},"/api/v1/plans":{"get":{"tags":["plans"],"summary":"List active plans","description":"All non-archived tiers in display order.","operationId":"list_4","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/PlanListResponse"}}}}}}},"/api/v1/metadata/search":{"get":{"tags":["metadata"],"summary":"Search a catalogue for the correction modal","operationId":"search","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string"}},{"name":"kind","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SearchResponse"}}}}}}},"/api/v1/health":{"get":{"tags":["health"],"summary":"Service health and build identity","description":"Returns 200 with service name, version, and build identifier.","operationId":"health","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"object","additionalProperties":{"type":"string"}}}}}},"security":[]}},"/api/v1/categories":{"get":{"tags":["categories"],"summary":"The download taxonomy","description":"Main categories with their subcategory keys.","operationId":"taxonomy","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/TaxonomyResponse"}}}}}}},"/api/v1/billing/subscription":{"get":{"tags":["billing"],"summary":"Current subscription","description":"The user's current plan + renewal/cancellation state. Returns has_plan=false when the user has no plan.","operationId":"subscription","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/SubscriptionResponse"}}}}}}},"/api/v1/billing/payments":{"get":{"tags":["billing"],"summary":"Payment history","description":"Every charge v2 made or tried for the account's plan, newest first. A checkout the owner abandoned on the payment page is not listed.","operationId":"listPayments","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int32"}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/Page"}}}}}}},"/api/v1/admin/users":{"get":{"tags":["admin"],"summary":"List users","description":"Cursor pagination, optional case-insensitive email substring filter.","operationId":"list_5","parameters":[{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int32"}},{"name":"q","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminListUsersResponse"}}}}}}},"/api/v1/admin/users/{id}":{"get":{"tags":["admin"],"summary":"Get single user detail","description":"Includes the user's current realm roles, a recent-torrents strip + total torrent count.","operationId":"get_6","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminUserDetailResponse"}}}}}},"delete":{"tags":["admin"],"summary":"Delete (soft-disable) a user account","description":"Soft-deletes the v2 row + disables the Keycloak user + revokes sessions. Refuses to delete your own account or another admin (409).","operationId":"delete_3","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK"}}}},"/api/v1/admin/users/{id}/torrents":{"get":{"tags":["admin"],"summary":"List a user's recent torrents","description":"Same projection as the detail endpoint's recent_torrents strip (info_hash, name, state). Optional limit (default 10, max 200).","operationId":"torrents","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminUserTorrentsResponse"}}}}}}},"/api/v1/admin/torrent-migrations/{id}":{"get":{"tags":["admin"],"operationId":"get_7","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/MigrationResponse"}}}}}}},"/api/v1/admin/torrent-migrations/targets":{"get":{"tags":["admin"],"summary":"Where a torrent can move","description":"Every storage node with free space, fill and cap, and why it cannot take the torrent.","operationId":"targets","parameters":[{"name":"torrent_id","in":"query","required":false,"schema":{"type":"integer","format":"int64"}},{"name":"info_hash","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TargetResponse"}}}}}}}},"/api/v1/admin/stats":{"get":{"tags":["admin"],"summary":"Dashboard counts","description":"Flat object of named counts: users (by status), torrents (by state), nodes (by status), payments, plans.","operationId":"stats","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminStatsResponse"}}}}}}},"/api/v1/admin/nodes":{"get":{"tags":["admin"],"summary":"List nodes","description":"Stable id-ascending order; no pagination — the cluster has < 16 nodes.","operationId":"list_6","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AdminNodeResponse"}}}}}}}},"/api/v1/admin/nodes/{id}/migrations-status":{"get":{"tags":["admin"],"summary":"Poll the soft-delete migration progress.","operationId":"migrationsStatus","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminMigrationsStatusResponse"}}}}}}},"/api/v1/admin/moderation/blocked":{"get":{"tags":["admin"],"summary":"BLOCKED verdicts (triage queue)","description":"reviewed=false for the unreviewed badge set.","operationId":"blocked","parameters":[{"name":"reviewed","in":"query","required":false,"schema":{"type":"boolean"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/BlockedListResponse"}}}}}}},"/api/v1/admin/moderation/accounts/{id}/events":{"get":{"tags":["admin"],"summary":"Evidence trail for one account","operationId":"events_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/EventsResponse"}}}}}}},"/api/v1/admin/moderation/accounts/blocked":{"get":{"tags":["admin"],"summary":"Accounts currently trust-and-safety blocked","operationId":"blockedAccounts","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/BlockedAccountsResponse"}}}}}}},"/api/v1/admin/migrations":{"get":{"tags":["admin"],"summary":"List all node soft-delete migrations","description":"Every node with a migration on record (migrations_status != none), across all nodes — backs the standalone admin migrations page.","operationId":"list_7","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AdminMigrationResponse"}}}}}}}},"/api/v1/admin/migrations/{id}":{"get":{"tags":["admin"],"summary":"Poll soft-delete migration progress","description":"The {id} is the source node id returned by the soft-delete call.","operationId":"get_8","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/AdminMigrationResponse"}}}}}}},"/api/v1/admin/jobs":{"get":{"tags":["admin"],"summary":"List maintenance jobs with their most recent run","operationId":"list_8","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/JobListResponse"}}}}}}},"/api/v1/admin/jobs/runs":{"get":{"tags":["admin"],"summary":"Recent job runs (optionally filtered by job key)","operationId":"runs","parameters":[{"name":"key","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int32"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/JobRunListResponse"}}}}}}},"/api/v1/admin/jobs/runs/{id}":{"get":{"tags":["admin"],"summary":"One job run","operationId":"run_2","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/JobRunResponse"}}}}}}},"/api/v1/admin/classification/regression":{"get":{"tags":["admin"],"summary":"Replay the owners' corrections through the steps without the model","operationId":"regression","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":200}}],"responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/Regression"}}}}}}},"/api/v1/accounts/me/api-key":{"get":{"tags":["accounts"],"summary":"Get the current account's API key metadata","description":"Never returns the cleartext key — only the 4-char hint + timestamps.","operationId":"getApiKey","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ApiKeyResponse"}}}}}},"delete":{"tags":["accounts"],"summary":"Revoke the current account's API key","description":"Idempotent — 204 whether or not a live key existed.","operationId":"revokeApiKey","responses":{"200":{"description":"OK"}}}},"/api/v1/billing/autopay/instruments/{id}":{"delete":{"tags":["Billing"],"summary":"Forget a saved card","operationId":"removeInstrument","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK"}}}}},"components":{"schemas":{"SaveProgressRequest":{"type":"object","properties":{"torrent_id":{"type":"integer","format":"int64","exclusiveMinimum":0},"file":{"type":"string","maxLength":4096,"minLength":0},"position_seconds":{"type":"integer","format":"int32","minimum":0},"duration_seconds":{"type":"integer","format":"int32","minimum":0}}},"PreferencesRequest":{"type":"object","properties":{"audio_lang":{"type":"string","maxLength":16,"minLength":0},"subtitle_lang":{"type":"string","maxLength":16,"minLength":0}}},"PreferencesResponse":{"type":"object","properties":{"audio_lang":{"type":"string"},"subtitle_lang":{"type":"string"}}},"PosterChoiceRequest":{"type":"object","properties":{"name":{"type":"string"}}},"Trailer":{"type":"object","properties":{"provider":{"type":"string"},"id":{"type":"string"},"language":{"type":"string"},"title":{"type":"string"}}},"WorkResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"category":{"type":"string"},"subcategory":{"type":"string"},"title":{"type":"string"},"original_title":{"type":"string"},"year":{"type":"integer","format":"int32"},"card_state":{"type":"string"},"description":{"type":"string"},"description_short":{"type":"string"},"description_lang":{"type":"string"},"poster_path":{"type":"string"},"poster_path_large":{"type":"string"},"trailer":{"$ref":"#/components/schemas/Trailer"}}},"SetTagsRequest":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string","maxLength":64,"minLength":0},"maxItems":50,"minItems":0}},"required":["tags"]},"MetadataSummary":{"type":"object","properties":{"status":{"type":"string"},"kind":{"type":"string"},"source":{"type":"string"},"source_id":{"type":"string"},"title":{"type":"string"},"original_title":{"type":"string"},"year":{"type":"integer","format":"int32"},"overview":{"type":"string"},"poster_url":{"type":"string"},"poster_url_large":{"type":"string"},"backdrop_url":{"type":"string"},"rating":{"type":"number","format":"double"},"manual":{"type":"boolean"},"resolved_at":{"type":"string","format":"date-time"},"genre_ids":{"type":"array","items":{"type":"integer","format":"int32"}},"origin_countries":{"type":"array","items":{"type":"string"}},"original_language":{"type":"string"}}},"TorrentResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"account_id":{"type":"integer","format":"int64"},"info_hash":{"type":"string"},"name":{"type":"string"},"total_bytes":{"type":"integer","format":"int64"},"source":{"type":"string"},"state":{"type":"string"},"node_assignment":{"type":"string"},"added_at":{"type":"string","format":"date-time"},"completed_at":{"type":"string","format":"date-time"},"error_message":{"type":"string"},"progress_pct":{"type":"integer","format":"int32"},"downloaded_bytes":{"type":"integer","format":"int64"},"download_speed_bps":{"type":"integer","format":"int64"},"tags":{"type":"array","items":{"type":"string"}},"metadata":{"$ref":"#/components/schemas/MetadataSummary"},"retry_count":{"type":"integer","format":"int32"},"next_retry_at":{"type":"string","format":"date-time"},"sample":{"type":"boolean"},"category":{"type":"string"},"subcategory":{"type":"string"},"category_source":{"type":"string"},"category_pending":{"type":"boolean"},"work":{"$ref":"#/components/schemas/WorkSummary"}}},"WorkSummary":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"title":{"type":"string"},"year":{"type":"integer","format":"int32"},"description_short":{"type":"string"},"description_lang":{"type":"string"},"poster_path":{"type":"string"},"card_state":{"type":"string"}}},"OverrideRequest":{"type":"object","properties":{"source":{"type":"string","maxLength":32,"minLength":0},"source_id":{"type":"string","maxLength":128,"minLength":0},"kind":{"type":"string","maxLength":16,"minLength":0}},"required":["kind","source","source_id"]},"CategoryRequest":{"type":"object","properties":{"category":{"type":"string","maxLength":32,"minLength":0},"subcategory":{"type":"string","maxLength":32,"minLength":0}}},"CategoryResponse":{"type":"object","properties":{"torrent_id":{"type":"integer","format":"int64"},"category":{"type":"string"},"subcategory":{"type":"string"},"category_source":{"type":"string"},"category_pending":{"type":"boolean"}}},"AutopayUpdateRequest":{"type":"object","properties":{"enabled":{"type":"boolean"},"instrument_id":{"type":"integer","format":"int64"}},"required":["enabled"]},"AutopayResponse":{"type":"object","properties":{"enabled":{"type":"boolean"},"instrument_id":{"type":"integer","format":"int64"},"card_masked":{"type":"string"},"card_type":{"type":"string"},"failed_attempts":{"type":"integer","format":"int32"},"next_attempt_at":{"type":"string"},"last_error":{"type":"string"},"last_charged_at":{"type":"string"},"provider":{"type":"string"},"instruments":{"type":"array","items":{"$ref":"#/components/schemas/InstrumentResponse"}}}},"InstrumentResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"provider":{"type":"string"},"card_masked":{"type":"string"},"card_type":{"type":"string"},"created_at":{"type":"string"}}},"AdminAssignPlanRequest":{"type":"object","properties":{"plan_code":{"type":"string","maxLength":64,"minLength":0},"ends_at":{"type":"string","format":"date-time"}},"required":["plan_code"]},"AdminUserPlanResponse":{"type":"object","properties":{"plan_code":{"type":"string"},"plan_name":{"type":"string"},"status":{"type":"string"},"source":{"type":"string"},"trial":{"type":"boolean"},"current_period_end":{"type":"string","format":"date-time"},"cancel_at":{"type":"string","format":"date-time"}}},"PlanUpsertRequest":{"type":"object","properties":{"code":{"type":"string","maxLength":64,"minLength":0},"display_name":{"type":"string","maxLength":128,"minLength":0},"monthly_price_cents":{"type":"integer","format":"int32","minimum":0},"annual_price_cents":{"type":"integer","format":"int32"},"max_concurrent_downloads":{"type":"integer","format":"int32","minimum":-1},"max_storage_gb":{"type":"integer","format":"int32","minimum":0},"seed_ratio_limit":{"type":"number","format":"double","maximum":100.0,"minimum":0.0},"seed_time_hours":{"type":"integer","format":"int32","minimum":0},"is_archived":{"type":"boolean"},"sort_order":{"type":"integer","format":"int32","minimum":0}},"required":["code","display_name"]},"PlanResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"code":{"type":"string"},"display_name":{"type":"string"},"monthly_price_cents":{"type":"integer","format":"int32"},"annual_price_cents":{"type":"integer","format":"int32"},"max_concurrent_downloads":{"type":"integer","format":"int32"},"max_storage_gb":{"type":"integer","format":"int32"},"seed_ratio_limit":{"type":"number","format":"double"},"seed_time_hours":{"type":"integer","format":"int32"},"is_archived":{"type":"boolean"}}},"AccountSettingsRequest":{"type":"object","properties":{"notify_download_complete":{"type":"boolean"},"hide_adult":{"type":"boolean"},"adult_hint_dismissed":{"type":"boolean"}},"required":["notify_download_complete"]},"AccountSettingsResponse":{"type":"object","properties":{"notify_download_complete":{"type":"boolean"},"hide_adult":{"type":"boolean"},"adult_hint_dismissed":{"type":"boolean"}}},"UploadTokenResponse":{"type":"object","properties":{"endpoint":{"type":"string"},"token":{"type":"string"},"expires_at":{"type":"integer","format":"int64"}}},"CreateTvSessionResponse":{"type":"object","properties":{"session_id":{"type":"string"},"user_code":{"type":"string"},"scan_path":{"type":"string"},"expires_in":{"type":"integer","format":"int64"}}},"TvApproveResponse":{"type":"object","properties":{"status":{"type":"string"}}},"TvExchangeRequest":{"type":"object","properties":{"session_id":{"type":"string","minLength":1},"exchange_code":{"type":"string","minLength":1}},"required":["exchange_code","session_id"]},"TvExchangeResponse":{"type":"object","properties":{"access_token":{"type":"string"},"refresh_token":{"type":"string"},"expires_at":{"type":"string","format":"date-time"}}},"TvApproveByCodeRequest":{"type":"object","properties":{"user_code":{"type":"string","minLength":1}},"required":["user_code"]},"CreateTorrentRequest":{"type":"object","properties":{"source":{"type":"string","minLength":1,"pattern":"magnet|url"},"value":{"type":"string","minLength":1}},"required":["source","value"]},"Move":{"type":"object","properties":{"path":{"type":"string","maxLength":1024,"minLength":0},"new_path":{"type":"string","maxLength":1024,"minLength":0}},"required":["new_path","path"]},"File":{"type":"object","properties":{"path":{"type":"string"},"size_bytes":{"type":"integer","format":"int64"},"mime_type":{"type":"string"},"is_video":{"type":"boolean"},"can_play":{"type":"boolean"},"download_url":{"type":"string"}}},"TorrentFileResponse":{"type":"object","properties":{"metadata_pending":{"type":"boolean"},"source":{"type":"string"},"files":{"type":"array","items":{"$ref":"#/components/schemas/File"}}}},"CreateFolder":{"type":"object","properties":{"path":{"type":"string","maxLength":1024,"minLength":0}},"required":["path"]},"CreateZipRequest":{"type":"object","properties":{"file_paths":{"type":"array","items":{"type":"string"},"minItems":1}},"required":["file_paths"]},"ZipTicketResponse":{"type":"object","properties":{"ticket_id":{"type":"string"},"state":{"type":"string"},"expires_at":{"type":"string","format":"date-time"},"download_url":{"type":"string"},"status_url":{"type":"string"},"error_message":{"type":"string"}}},"CreateMultiZipRequest":{"type":"object","properties":{"torrent_ids":{"type":"array","items":{"type":"integer","format":"int64"},"maxItems":100,"minItems":0}},"required":["torrent_ids"]},"BulkActionRequest":{"type":"object","properties":{"action":{"type":"string","pattern":"delete|pause|resume|retry"},"ids":{"type":"array","items":{"type":"integer","format":"int64"},"maxItems":500,"minItems":1}},"required":["action","ids"]},"BulkActionResponse":{"type":"object","properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/Item"}}}},"Item":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"ok":{"type":"boolean"},"error":{"type":"string"}}},"AttachmentResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"filename":{"type":"string"},"size":{"type":"integer","format":"int64"},"content_type":{"type":"string"}}},"MessageResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"author":{"type":"string"},"author_name":{"type":"string"},"body":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/AttachmentResponse"}}}},"TicketDetailResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"number":{"type":"string"},"category":{"type":"string"},"subject":{"type":"string"},"status":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"awaiting_customer":{"type":"boolean"},"messages":{"type":"array","items":{"$ref":"#/components/schemas/MessageResponse"}}}},"OpenTicketRequest":{"type":"object","properties":{"category":{"type":"string","pattern":"general|billing|download|streaming|account|abuse"},"subject":{"type":"string","maxLength":200,"minLength":0},"message":{"type":"string","maxLength":10000,"minLength":0}},"required":["message","subject"]},"ReplyRequest":{"type":"object","properties":{"message":{"type":"string","maxLength":10000,"minLength":0}},"required":["message"]},"ContactRequest":{"type":"object","properties":{"email":{"type":"string","format":"email","maxLength":254,"minLength":0},"category":{"type":"string","pattern":"general|billing|download|streaming|account|abuse"},"subject":{"type":"string","maxLength":200,"minLength":0},"message":{"type":"string","maxLength":10000,"minLength":0},"website":{"type":"string","maxLength":200,"minLength":0}},"required":["email","message","subject"]},"SubscriptionResponse":{"type":"object","properties":{"has_plan":{"type":"boolean"},"plan":{"$ref":"#/components/schemas/PlanResponse"},"next_renewal_at":{"type":"string"},"period":{"type":"string"},"cancel_at":{"type":"string"},"status":{"type":"string"},"source":{"type":"string"},"trial":{"type":"boolean"},"ends_at":{"type":"string"},"scheduled_plan":{"$ref":"#/components/schemas/PlanResponse"},"days_left":{"type":"integer","format":"int32"},"grace_until":{"type":"string"}}},"PlanChangeRequest":{"type":"object","properties":{"plan_id":{"type":"integer","format":"int64"},"period":{"type":"string","pattern":"monthly|annual"},"expected_amount_cents":{"type":"integer","format":"int64"},"hosted":{"type":"boolean"},"return_url":{"type":"string"},"language":{"type":"string"}},"required":["plan_id"]},"CheckoutResponse":{"type":"object","properties":{"checkout_id":{"type":"integer","format":"int64"},"order_reference":{"type":"string"},"provider":{"type":"string"},"action":{"type":"string"},"fields":{"type":"object","additionalProperties":{"type":"string"}}}},"PlanChangeResponse":{"type":"object","properties":{"kind":{"type":"string"},"status":{"type":"string"},"amount_cents":{"type":"integer","format":"int64"},"currency":{"type":"string"},"reason":{"type":"string"},"subscription":{"$ref":"#/components/schemas/SubscriptionResponse"},"checkout":{"$ref":"#/components/schemas/CheckoutResponse"}}},"CheckoutRequest":{"type":"object","properties":{"plan_id":{"type":"integer","format":"int64"},"period":{"type":"string","pattern":"monthly|annual"},"return_url":{"type":"string"},"language":{"type":"string"}},"required":["plan_id"]},"CancelRequest":{"type":"object","properties":{"reason":{"type":"string"}}},"CreateRuleRequest":{"type":"object","properties":{"name":{"type":"string","maxLength":80,"minLength":0},"trigger":{"type":"string","minLength":1,"pattern":"rss|completed|retention"},"config":{"$ref":"#/components/schemas/JsonNode"}},"required":["name","trigger"]},"JsonNode":{},"RuleResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"trigger":{"type":"string"},"enabled":{"type":"boolean"},"config":{"$ref":"#/components/schemas/JsonNode"},"run_count":{"type":"integer","format":"int32"},"last_run_at":{"type":"string","format":"date-time"},"last_result":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}},"RunResponse":{"type":"object","properties":{"result":{"type":"string"}}},"AdminUserResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"keycloak_user_id":{"type":"string","format":"uuid"},"email":{"type":"string"},"display_name":{"type":"string"},"locale":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"status":{"type":"string"},"suspended_at":{"type":"string","format":"date-time"},"suspended_reason":{"type":"string"},"total_torrents":{"type":"integer","format":"int64"}}},"AdminSuspendRequest":{"type":"object","properties":{"reason":{"type":"string","maxLength":500,"minLength":0}},"required":["reason"]},"AdminRoleRequest":{"type":"object","properties":{"role":{"type":"string","minLength":1},"roleRecognised":{"type":"boolean"}},"required":["role"]},"AdminImpersonateResponse":{"type":"object","properties":{"access_token":{"type":"string"},"expires_at":{"type":"string","format":"date-time"},"target_user_id":{"type":"integer","format":"int64"}}},"MoveRequest":{"type":"object","properties":{"torrent_id":{"type":"integer","format":"int64"},"info_hash":{"type":"string"},"target_node":{"type":"string"}}},"MigrationResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"info_hash":{"type":"string"},"torrent_name":{"type":"string"},"src_node":{"type":"string"},"dst_node":{"type":"string"},"requested_dst":{"type":"string"},"state":{"type":"string"},"attempts":{"type":"integer","format":"int32"},"bytes_total":{"type":"integer","format":"int64"},"bytes_done":{"type":"integer","format":"int64"},"last_error":{"type":"string"},"drain":{"type":"boolean"},"cancellable":{"type":"boolean"},"retryable":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"finished_at":{"type":"string","format":"date-time"}}},"DrainRequest":{"type":"object","properties":{"source_node":{"type":"string","minLength":1},"target_node":{"type":"string"}},"required":["source_node"]},"DrainResponse":{"type":"object","properties":{"enqueued":{"type":"integer","format":"int32"},"skipped_in_flight":{"type":"integer","format":"int32"},"skipped_busy":{"type":"integer","format":"int32"}}},"AdminSoftDeleteNodeRequest":{"type":"object","properties":{"migrations":{"$ref":"#/components/schemas/Migrations"}},"required":["migrations"]},"Migrations":{"type":"object","properties":{"target_node_id":{"type":"integer","format":"int64","exclusiveMinimum":0}}},"AdminNodeResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"hostname":{"type":"string"},"ip_address":{"type":"string"},"capacity_gb":{"type":"integer","format":"int64"},"used_gb":{"type":"integer","format":"int64"},"status":{"type":"string"},"max_fill_percent":{"type":"integer","format":"int32"},"fill_percent":{"type":"integer","format":"int32"},"migrations_status":{"type":"string"},"migrations_target_node_id":{"type":"integer","format":"int64"},"migrations_processed":{"type":"integer","format":"int32"},"migrations_total":{"type":"integer","format":"int32"},"migrations_last_error":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"soft_deleted_at":{"type":"string","format":"date-time"}}},"ReviewRequest":{"type":"object","properties":{"reviewed":{"type":"boolean"}}},"ModerationVerdictSummary":{"type":"object","properties":{"torrent_id":{"type":"integer","format":"int64"},"status":{"type":"string"},"reason":{"type":"string"},"source":{"type":"string"},"checked_at":{"type":"string","format":"date-time"},"reacted_at":{"type":"string","format":"date-time"},"reviewed":{"type":"boolean"},"reviewed_by":{"type":"integer","format":"int64"},"reviewed_at":{"type":"string","format":"date-time"},"account_id":{"type":"integer","format":"int64"},"info_hash":{"type":"string"},"torrent_name":{"type":"string"}}},"ReasonRequest":{"type":"object","properties":{"reason":{"type":"string","maxLength":500,"minLength":0}}},"BlockState":{"type":"object","properties":{"account_id":{"type":"integer","format":"int64"},"email":{"type":"string"},"blocked_at":{"type":"string","format":"date-time"},"blocked_reason":{"type":"string"},"blocked":{"type":"boolean"}}},"JobRunResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"job_key":{"type":"string"},"status":{"type":"string"},"triggered_by":{"type":"string"},"started_at":{"type":"string","format":"date-time"},"finished_at":{"type":"string","format":"date-time"},"result":{"type":"string"},"error_message":{"type":"string"}}},"ChangePasswordRequest":{"type":"object","properties":{"current_password":{"type":"string","minLength":1},"new_password":{"type":"string","maxLength":256,"minLength":8}},"required":["current_password","new_password"]},"Rotated":{"type":"object","properties":{"key":{"type":"string"},"hint":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}},"UpdateRuleRequest":{"type":"object","properties":{"name":{"type":"string","maxLength":80,"minLength":0},"enabled":{"type":"boolean"},"config":{"$ref":"#/components/schemas/JsonNode"}}},"AdminNodeSettingsRequest":{"type":"object","properties":{"max_fill_percent":{"type":"integer","format":"int32","maximum":100,"minimum":1}},"required":["max_fill_percent"]},"UpdateAccountRequest":{"type":"object","properties":{"display_name":{"type":"string","maxLength":100,"minLength":0},"locale":{"type":"string","pattern":"en|ru|uk|de|es|pt|zh|hi"}}},"AccountResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"email":{"type":"string"},"display_name":{"type":"string"},"locale":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"api_key_present":{"type":"boolean"},"roles":{"type":"array","items":{"type":"string"}}}},"ProgressListResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ProgressResponse"}}}},"ProgressResponse":{"type":"object","properties":{"torrent_id":{"type":"integer","format":"int64"},"file":{"type":"string"},"position_seconds":{"type":"integer","format":"int32"},"duration_seconds":{"type":"integer","format":"int32"},"finished":{"type":"boolean"},"updated_at":{"type":"string","format":"date-time"}}},"TvSessionStatusResponse":{"type":"object","properties":{"status":{"type":"string"},"exchange_code":{"type":"string"}}},"ListTorrentsResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/TorrentResponse"}},"next_cursor":{"type":"string"}}},"PosterChoice":{"type":"object","properties":{"name":{"type":"string"},"path":{"type":"string"}}},"PosterChoices":{"type":"object","properties":{"posters":{"type":"array","items":{"$ref":"#/components/schemas/PosterChoice"}}}},"SseEmitter":{"type":"object","properties":{"timeout":{"type":"integer","format":"int64"}}},"TorrentProgressResponse":{"type":"object","properties":{"state":{"type":"string"},"progress_pct":{"type":"integer","format":"int32"},"downloaded_bytes":{"type":"integer","format":"int64"},"total_bytes":{"type":"integer","format":"int64"},"download_speed_bps":{"type":"integer","format":"int64"},"upload_speed_bps":{"type":"integer","format":"int64"},"eta_seconds":{"type":"integer","format":"int64"},"connected_peers":{"type":"integer","format":"int32"},"seeding":{"type":"boolean"},"uploaded_bytes":{"type":"integer","format":"int64"},"ratio":{"type":"number","format":"double"}}},"DownloadLinkResponse":{"type":"object","properties":{"url":{"type":"string"},"expires_at":{"type":"integer","format":"int64"}}},"TorrentUsageResponse":{"type":"object","properties":{"plan_code":{"type":"string"},"plan_display_name":{"type":"string"},"subscription_status":{"type":"string"},"trial":{"type":"boolean"},"current_period_end":{"type":"string","format":"date-time"},"cancel_at":{"type":"string","format":"date-time"},"storage_used_bytes":{"type":"integer","format":"int64"},"storage_limit_bytes":{"type":"integer","format":"int64"},"active_downloads":{"type":"integer","format":"int64"},"max_concurrent_downloads":{"type":"integer","format":"int32"},"total_torrents":{"type":"integer","format":"int64"}}},"TagItem":{"type":"object","properties":{"tag":{"type":"string"},"count":{"type":"integer","format":"int64"}}},"TagListResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/TagItem"}}}},"TicketListResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/TicketResponse"}}}},"TicketResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"number":{"type":"string"},"category":{"type":"string"},"subject":{"type":"string"},"status":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"awaiting_customer":{"type":"boolean"}}},"CanPlayResponse":{"type":"object","properties":{"info_hash":{"type":"string"},"media_path_relative":{"type":"string"},"state":{"type":"string"}}},"PlanListResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PlanResponse"}}}},"MetadataCandidate":{"type":"object","properties":{"source":{"type":"string"},"source_id":{"type":"string"},"kind":{"type":"string"},"title":{"type":"string"},"original_title":{"type":"string"},"year":{"type":"integer","format":"int32"},"overview":{"type":"string"},"poster_url":{"type":"string"},"rating":{"type":"number","format":"double"},"genre_ids":{"type":"array","items":{"type":"integer","format":"int32"}},"origin_countries":{"type":"array","items":{"type":"string"}},"original_language":{"type":"string"}}},"SearchResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/MetadataCandidate"}}}},"Entry":{"type":"object","properties":{"key":{"type":"string"},"subcategories":{"type":"array","items":{"type":"string"}}}},"TaxonomyResponse":{"type":"object","properties":{"categories":{"type":"array","items":{"$ref":"#/components/schemas/Entry"}}}},"PlanChangePreviewResponse":{"type":"object","properties":{"kind":{"type":"string"},"current_plan":{"$ref":"#/components/schemas/PlanResponse"},"target_plan":{"$ref":"#/components/schemas/PlanResponse"},"period":{"type":"string"},"amount_cents":{"type":"integer","format":"int64"},"currency":{"type":"string"},"remaining_days":{"type":"integer","format":"int32"},"period_days":{"type":"integer","format":"int32"},"effective_at":{"type":"string"},"current_period_end":{"type":"string"},"card_masked":{"type":"string"},"autopay_enabled":{"type":"boolean"}}},"Page":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PaymentResponse"}},"next_cursor":{"type":"string"}}},"PaymentResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"kind":{"type":"string"},"status":{"type":"string"},"plan_code":{"type":"string"},"plan_name":{"type":"string"},"period":{"type":"string"},"amount_cents":{"type":"integer","format":"int64"},"currency":{"type":"string"},"card_masked":{"type":"string"},"reference":{"type":"string"},"created_at":{"type":"string"},"settled_at":{"type":"string"}}},"RuleListResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/RuleResponse"}}}},"AdminListUsersResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/AdminUserResponse"}},"next_cursor":{"type":"string"}}},"AdminUserDetailResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"keycloak_user_id":{"type":"string","format":"uuid"},"email":{"type":"string"},"display_name":{"type":"string"},"locale":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"status":{"type":"string"},"suspended_at":{"type":"string","format":"date-time"},"suspended_reason":{"type":"string"},"total_torrents":{"type":"integer","format":"int64"},"roles":{"type":"array","items":{"type":"string"}},"plan":{"$ref":"#/components/schemas/AdminUserPlanResponse"},"recent_torrents":{"type":"array","items":{"$ref":"#/components/schemas/RecentTorrent"}}}},"RecentTorrent":{"type":"object","properties":{"info_hash":{"type":"string"},"name":{"type":"string"},"state":{"type":"string"}}},"AdminUserTorrentsResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/RecentTorrent"}}}},"TargetResponse":{"type":"object","properties":{"node":{"type":"string"},"current":{"type":"boolean"},"free_gb":{"type":"integer","format":"int64"},"total_gb":{"type":"integer","format":"int64"},"fill_percent":{"type":"integer","format":"int32"},"max_fill_percent":{"type":"integer","format":"int32"},"eligible":{"type":"boolean"},"reason":{"type":"string"}}},"AdminStatsResponse":{"type":"object","properties":{"total_users":{"type":"integer","format":"int64"},"active_users":{"type":"integer","format":"int64"},"suspended_users":{"type":"integer","format":"int64"},"total_torrents":{"type":"integer","format":"int64"},"torrents_queued":{"type":"integer","format":"int64"},"torrents_downloading":{"type":"integer","format":"int64"},"torrents_completed":{"type":"integer","format":"int64"},"torrents_errored":{"type":"integer","format":"int64"},"torrents_removed":{"type":"integer","format":"int64"},"total_nodes":{"type":"integer","format":"int64"},"nodes_active":{"type":"integer","format":"int64"},"nodes_maintenance":{"type":"integer","format":"int64"},"nodes_soft_deleted":{"type":"integer","format":"int64"},"total_payments":{"type":"integer","format":"int64"},"paid_payments":{"type":"integer","format":"int64"},"total_plans":{"type":"integer","format":"int64"},"active_plans":{"type":"integer","format":"int64"}}},"AdminMigrationsStatusResponse":{"type":"object","properties":{"state":{"type":"string"},"processed":{"type":"integer","format":"int32"},"total":{"type":"integer","format":"int32"},"last_processed_at":{"type":"string","format":"date-time"},"error":{"type":"string"}}},"BlockedListResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ModerationVerdictSummary"}},"unreviewed_count":{"type":"integer","format":"int64"}}},"EventItem":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"account_id":{"type":"integer","format":"int64"},"torrent_id":{"type":"integer","format":"int64"},"info_hash":{"type":"string"},"torrent_name":{"type":"string"},"action":{"type":"string"},"reason":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}},"EventsResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/EventItem"}}}},"BlockedAccountsResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/BlockState"}}}},"AdminMigrationResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"status":{"type":"string"},"processed":{"type":"integer","format":"int32"},"total":{"type":"integer","format":"int32"},"target_node_id":{"type":"integer","format":"int64"},"last_error":{"type":"string"},"started_at":{"type":"string","format":"date-time"},"finished_at":{"type":"string","format":"date-time"}}},"JobItem":{"type":"object","properties":{"key":{"type":"string"},"description":{"type":"string"},"destructive":{"type":"boolean"},"last_run":{"$ref":"#/components/schemas/JobRunResponse"}}},"JobListResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/JobItem"}}}},"JobRunListResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/JobRunResponse"}}}},"Regression":{"type":"object","properties":{"total":{"type":"integer","format":"int32"},"agree_now":{"type":"integer","format":"int32"},"need_model":{"type":"integer","format":"int32"},"agreed_before":{"type":"integer","format":"int32"},"cases":{"type":"array","items":{"$ref":"#/components/schemas/Replayed"}}}},"Replayed":{"type":"object","properties":{"torrent_id":{"type":"integer","format":"int64"},"name":{"type":"string"},"chosen":{"type":"string"},"pipeline_said":{"type":"string"},"now":{"type":"string"},"step":{"type":"string"},"agrees":{"type":"boolean"}}},"ApiKeyResponse":{"type":"object","properties":{"present":{"type":"boolean"},"hint":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"last_used_at":{"type":"string","format":"date-time"}}}},"securitySchemes":{"bearerAuth":{"type":"http","description":"Keycloak-issued access token (see V2_PLAN.md section 5).","scheme":"bearer","bearerFormat":"JWT"},"apiKeyAuth":{"type":"apiKey","description":"Personal API key from Account -> API key. Same scope as the user's own session (never admin). 120 requests/minute per key.","name":"X-API-Key","in":"header"}}}}