Skip to content

API reference ​

Trackarr exposes three surfaces:

  1. Tracker endpoints — apps/tracker (Go). Public, anonymous, BEP-spec'd. /announce over HTTP and UDP.
  2. REST API — apps/api (Nitro). Everything under /api/*. Session-cookie auth.
  3. Torznab + RSS — feed-style endpoints for *Arr clients and feed readers, keyed by passkey or Torznab API key.

The lists below are curated, not exhaustive: they cover the surfaces an integrator or an operator reaches for, with the notes a generator cannot write. The complete, always-current inventory is generated from the route tree itself and served by the running instance:

GET /api/docs/openapi.json      # every path, method, guard and shared schema

apps/api/scripts/generate-openapi.mjs derives it from the filenames, so a path cannot exist without appearing there, nor appear without existing. Whole subsystems (messaging, federation, themes, presentation templates, tickets, site statistics) are documented in their own guides and in that document rather than duplicated here.

Auth labels: public (anonymous), user (any authenticated user), mod (canModerate permission), admin (admin role), passkey (announce-style, passkey in URL).

Tracker endpoints ​

The Go tracker speaks two protocols off the same backend.

HTTP announce (BEP 3) — GET /announce ​

GET /announce?info_hash=...&peer_id=...&port=...
            &uploaded=...&downloaded=...&left=...
            &event=started|completed|stopped&numwant=...&compact=1

Passkey is read from the path (/announce/PASSKEY) or the ?passkey= query parameter. Response is the standard bencoded peer list.

What the answer depends on, over both transports:

SituationAnswer
The announcer is a seeder (left=0)Leechers only — a seeder has nothing to fetch from another. complete / incomplete still count the whole swarm
A member's sixth live client session on one torrentAccepted; their oldest entry is evicted. Five per member per torrent
A peer_id another member is announcing underServed from an entry of its own (peer_id:user_id); neither member can read, overwrite or remove the other's
120 unknown passkeys from one address within a minute (an IPv6 /64 counts as one)Too many invalid requests, retry later for the rest of that minute, valid passkeys included. Counted per tracker replica
Redis unreachableNo peers, interval 300 and min interval 60, so clients come back soon after it does; nothing is credited

UDP announce (BEP 15) — :6969/udp ​

Stateless connect-then-announce. Passkey extracted from the BEP 41 URL_DATA option. See UDP Tracker for the wire format, connection-id HMAC scheme, and reverse-proxy caveats.

HTTP scrape (BEP 48) — GET /scrape ​

Up to 64 info_hash parameters per request, and the passkey is required (?passkey=): BEP 48 does not ask for one, a private tracker does, or anyone could confirm what the site carries and how popular it is. Clients derive the scrape URL from the announce URL, query string included, so nothing needs configuring. Unknown passkeys count towards the same per-address limit as announces. UDP scrape takes no passkey, by protocol, and can be closed with TRACKER_UDP_SCRAPE_ENABLED=false.

No wss://

The tracker has no WebTorrent (WebSocket) announce path. Don't advertise a wss:// URL in .torrent files — clients will hang on it.

Public (no auth) ​

MethodPathPurpose
GET/api/healthReadiness probe, public and unauthenticated. Returns 200 {status, uptime, checks:{postgres,redis}} with per-component latency — it reports on dependencies, so treat it as a liveness signal you are choosing to publish.
GET/api/brandingSite title, tagline, logo URL, favicon URL, feature flags.
GET/api/runtime-configPublic Nuxt runtime config (announce URLs, etc.) for the static SPA.
GET/api/announcementOperator-set homepage banner (text + level).
GET/api/rulesThe site rules from Admin → Branding, as HTML ({ rules }), shown at /rules.
GET/api/homepage-contentFeatured rails, custom blocks (operator-curated).
GET/api/categoriesHierarchical category tree.
GET/api/tagsTag master list (autocomplete source).
GET/api/upload-rulesPublic upload constraints (max size, allowed extensions, …).
GET/api/stats/publicSwarm counters (peers, seeders, torrents, users) + UDP toggle matrix.
GET/api/tracker-healthTracker availability matrix (HTTP up/down, UDP up/down).
GET/api/torrentsBrowse torrents (page, limit, category, tags, q, sort).
GET/api/torrents/:hashTorrent detail (metadata + swarm + comments).
GET/api/users/:idPublic profile (display name, role badges, redacts last-seen if opted out).
GET/api/users/:id/uploadsPaginated list of accepted torrents from this user.

Authentication ​

Zero-Knowledge: the password never leaves the browser. See the Zero-Knowledge Auth guide.

MethodPathAuthPurpose
GET/api/auth/powpublicIssue a Proof-of-Work challenge for the registration form.
POST/api/auth/registerpublicCreate a user — body: { username, salt, verifier, invite?, pow }.
GET/api/auth/challengepublicReturn { salt, challenge } keyed by username for the ZK login proof.
POST/api/auth/loginpublicVerify { username, proof, challenge } and open a sealed-cookie session.
POST/api/auth/logoutuserRevoke the current session.
GET/api/auth/statuspublicReturns current user + locale + theme (or null if anonymous).
PUT/api/auth/passworduserAtomic password rotation: prove current, ship new { salt, verifier }.
GET/api/auth/passkeypublicWebAuthn assertion options for first-factor passkey login.
POST/api/auth/passkeypublicVerify a WebAuthn assertion and open a session.

Two-factor (during sign-in) ​

MethodPathAuthPurpose
POST/api/auth/2fa/passkey-optionsuser (1FA)WebAuthn options for the second-factor step.
POST/api/auth/2fa/passkey-verifyuser (1FA)Verify the second-factor passkey, finalise the session.
POST/api/auth/2fa/verify-totpuser (1FA)Verify a 6-digit TOTP code (with optional trust_device).

Two-factor enrolment (logged in) ​

MethodPathAuthPurpose
GET/api/me/2fa/statususerTOTP enabled? Passkey list? Trust-device flag?
POST/api/me/2fa/totp/setupuserGenerate a TOTP secret + recovery codes.
POST/api/me/2fa/totp/enableuserConfirm with a 6-digit code → activates TOTP.
POST/api/me/2fa/totp/disableuserDisable TOTP (requires a fresh 6-digit code).
POST/api/me/2fa/recovery-codes/regenerateuserRotate the recovery code set.
POST/api/me/2fa/passkey/register-challengeuserWebAuthn attestation options to add a passkey.
POST/api/me/2fa/passkey/register-verifyuserVerify and store a new passkey.
DELETE/api/me/2fa/passkey/:iduserRemove a passkey by id.
GET/api/me/2fa/sessionsuserList trusted devices.
DELETE/api/me/2fa/sessions/:iduserRevoke a trusted device.
PUT/api/me/2fa/trust-devicesuserToggle the "trust this browser for 30 days" flag.

Profile (/me) ​

MethodPathAuthPurpose
GET/api/meuserFull profile snapshot (KPIs, role badges, bio, locale, theme).
GET/api/welcomeuserThe staff's welcome note ({ message }, HTML), shown on the home page.
PATCH/api/meuserUpdate display name, bio, locale, theme, last-seen privacy toggle.
GET/api/me/downloadsuser.torrent downloads + per-download HnR state.
GET/api/me/seedsuserActive seeds with live swarm stats.
GET/api/me/bonus-historyuserLedger of credited bonus points (paginated, oldest → newest).
GET/api/users/hnruserCurrent user's HnR violations (shortcut).

Notifications ​

The in-app inbox is always-on; external channels are opt-in per user.

MethodPathAuthPurpose
GET/api/me/notificationsuserPaginated inbox.
GET/api/me/notifications/streamuserServer-Sent Events stream — live push to open tabs (Redis pub/sub fanout).
POST/api/me/notifications/:id/readuserMark a single row as read.
POST/api/me/notifications/read-alluserMark every row as read.
GET/api/me/notification-channelsuserChannels configured for this user (and their state).
PUT/api/me/notification-channels/:typeuserConfigure / update a channel (encrypted at rest).
DELETE/api/me/notification-channels/:typeuserDisable a channel.
POST/api/me/notification-channels/:type/testuserSend a test notification through this channel.
PUT/api/me/notification-routinguserMap {notification_type → channel_type} for fan-out.

See Notifications for the channel types (SMTP, Telegram, Discord, ntfy, Gotify, Pushover, Slack, Mattermost, webhook, Apprise, Web Push).

Torrents ​

MethodPathAuthPurpose
GET/api/torrentsuserThe flat listing. Filters: search, categoryId, tag, tagGroups, imdbid/tmdbid/tvdbid, uploader, year, season, episode, minSeeders, freeleech, notTaken, hideSuperseded, favorites, `since=24h
GET/api/torrents/groupsuserThe same query, grouped by work — one row per film / series / album, with its scopes. `sources=local
GET/api/torrents/groupuserOne work's releases, bucketed by season / episode / integral.
GET/api/torrents/facetsuserCounts for the current query: categories, tags per family, years, member options, plus what each dropped criterion would return. Cached 20 s per member.
POST/api/torrentsuserUpload (multipart: .torrent + metadata + optional NFO).
POST/api/torrents/checkuserPre-flight infohash lookup before opening the upload form.
GET/api/torrents/:hashpublicDetail page payload.
PATCH/api/torrents/:hashuploader / modEdit title / description / tags / external IDs.
DELETE/api/torrents/:hashuploader / modSoft-delete.
GET/api/torrents/:hash/downloaduser.torrent with the user's passkey baked into the announce list.
PUT/api/torrents/:hash/tagsuploader / modReplace the tag set.
POST/api/torrents/:hash/commentsuserPost a comment.
DELETE/api/torrents/comments/:idauthor / modDelete a comment.
GET/api/torrents/:hash/moderation/messagesthread†Moderation chat — {status, messages}. 404 if not allowed.
POST/api/torrents/:hash/moderation/messagesthread†Reply without a status change.
GET/api/torrents/:hash/commentsuserOlder comments, by cursor (before + beforeId, limit ≤ 50). The detail payload carries the first twenty and the total.
GET/api/torrents/:hash/my-obligationuserThe caller's own seed obligation for this release, or null when Hit & Run is off.
GET/api/torrents/:hash/stats-historyuserSeven days of swarm counts, one point per day the collector ran.
GET/api/torrents/:hash/cross-seedsuserSibling torrents that share this content signature (up to 50).
GET/api/torrents/:hash/cross-seed-statsuserCross-seed KPI bundle (peer overlap + volume share). Memoised 30 s.
POST/api/torrents/:hash/favoriteuserStar (idempotent).
DELETE/api/torrents/:hash/favoriteuserUnstar (silent if not starred).
GET/api/debug/:hashmodRaw row + per-peer swarm dump.

† thread = uploader of the row OR any staff member.

Favorites ​

MethodPathAuthPurpose
GET/api/me/favoritesuserPaginated catalogue, sort recent / name / seeders.

Follows (social graph) ​

MethodPathAuthPurpose
POST/api/users/:id/followuserSubscribe to a user (idempotent, refuses self-follow).
DELETE/api/users/:id/followuserUnsubscribe (silent if not following).
GET/api/me/followinguserPaginated cast list, sort recent / alpha.

The public /api/users/:id payload carries followersCount and viewerFollowing so the profile page renders the toggle state in one round-trip.

Reports (user-side) ​

MethodPathAuthPurpose
POST/api/reportsuserSubmit a report ({ target_type, target_id, reason, details? }). See Reports.
GET/api/me/reportsuserList the caller's filed reports + current status.
DELETE/api/me/reports/:iduserWithdraw a pending report (hard-delete; refused once staff has acted).

Upload requests (bounty board) ​

MethodPathAuthPurpose
GET/api/requestsuserPaginated list. Filters: status, mine, categoryId, search.
POST/api/requestsuserCreate. Reward (if any) debited atomically from bonus_points.
GET/api/requests/:iduserDetail + thread + fill-attempt history + viewer permissions.
PATCH/api/requests/:idrequesterEdit while requested. Reward bump-only.
DELETE/api/requests/:idrequesterCancel + refund. Only while requested.
POST/api/requests/:id/filluser (not requester)Propose a torrent ({ infoHash } or { torrentId }). Enforces uploader = caller, category match, per-user cap.
POST/api/requests/:id/validaterequesterAccept the fill + pay the filler.
POST/api/requests/:id/rejectrequesterBounce the fill; status → requested; filler attempts counter ticks.
POST/api/requests/:id/commentsuserAppend to the discussion (locked when validated/cancelled).
PATCH/api/requests/:id/comments/:cidauthor (15 min)Edit own comment within the grace window.
DELETE/api/requests/:id/comments/:cidauthor (15 min) / modSoft-delete (body hidden, row stays).

See Upload Requests for the full lifecycle, point ledger, and operator settings.

Anti-cheat (staff) ​

MethodPathAuthPurpose
GET/api/mod/anti-cheat/flagsmodPaginated queue. Filters: status, kind, userId.
PUT/api/mod/anti-cheat/flags/:idmodStamp a review verdict + optional note. See Anti-cheat.

Moderation ​

MethodPathAuthPurpose
GET/api/mod/dashboardmodCounters: pending uploads, pending reports, HnR violations, …
GET/api/mod/torrents/pendingmodPending upload queue.
POST/api/mod/torrents/:hash/approvemodMove row → accepted. Optional message.
POST/api/mod/torrents/:hash/request-changesmodMove row → changes_requested. Message required.
POST/api/mod/torrents/:hash/rejectmodMove row → rejected. Message required.
POST/api/mod/torrents/:hash/resetmodOnly path out of rejected. Message required.

Forum ​

MethodPathAuthPurpose
GET/api/forum/categoriespublicForum structure.
GET/api/forum/categories/:idpublicCategory + paginated topics.
POST/api/forum/categoriesadminCreate a category.
PUT/api/forum/categories/:idadminEdit a category.
DELETE/api/forum/categories/:idadminRemove a category.
POST/api/forum/topicsuserCreate a topic.
GET/api/forum/topics/:idpublicTopic + paginated posts.
DELETE/api/forum/topics/:idauthor / modDelete topic.
PUT/api/forum/topics/:id/pinmodToggle pin.
PUT/api/forum/topics/:id/lockmodToggle lock.
POST/api/forum/postsuserReply in a topic.
PATCH/api/forum/posts/:idauthor / modEdit a post.
DELETE/api/forum/posts/:idauthor / modDelete a post.
GET/api/forum/statspublicAggregate stats (topic count, post count, latest reply).

Bonus economy & shop ​

MethodPathAuthPurpose
GET/api/bonus/activepublicActive freeleech / silverleech / custom events.
GET/api/shop/itemsuserShop catalogue.
POST/api/shop/buyuserPurchase an item ({ item_id }). Transactional against users.bonus_points.

Invitations ​

MethodPathAuthPurpose
GET/api/invitesuserMy invite codes (remaining quota + outstanding codes).
POST/api/invitesuserGenerate a code (decrements users.invites_remaining).
DELETE/api/invites/:iduserRevoke an unused code.

Metadata lookup ​

Filename → external metadata (TMDb / IGDB / Open Library / Google Books). Locale-aware via the user's profile setting.

MethodPathAuthPurpose
GET/api/metadata/searchuserMulti-source search (auto-routes by category type).
GET/api/metadata/lookupuserResolve a known external id to a normalised payload.
POST/api/metadata/tintuserStore the dominant colour of a work's poster ({source, id, tint: "R G B"}), computed by the browser on the detail page and reused by the catalogue's cards. Accepted only for a work the metadata cache already holds; the answer never says whether it did.

A missing provider key returns 503 only on the routes that need that source — the others keep serving. See Metadata providers.

Torznab & RSS ​

Exposed for *Arr clients (Sonarr / Radarr / …) and feed readers.

MethodPathAuthPurpose
GET / HEAD/api/torznab/apitorznab keyNewznab/Torznab dispatcher (`?t=caps
GET/api/torznab/downloadtorznab keyDownload a .torrent keyed by r=<torznab-key>.
GET/api/rss/latestpublicRecent torrents (Atom feed).
GET/api/rss/category/:slugpublicCategory-scoped feed.

See Torznab integration for the *Arr-client wiring.

Admin ​

The admin surface is gated by the canAccessAdmin permission. Auth = admin below.

Users & roles ​

MethodPathPurpose
GET/api/admin/usersPaginated registry with KPI strip + filters.
POST/api/admin/users/:id/banBan (auto-banlist the last-known IP). Accepts an optional duration (days).
POST/api/admin/users/:id/unbanUnban (does not auto-remove the IP banlist row). Refuses a mod trying to lift an admin-issued ban.
PUT/api/admin/users/:id/roleDeprecated. Use the role-attach endpoints below.
GET/api/admin/users/:id/rolesList a user's roles.
POST/api/admin/users/:id/rolesAttach a role.
DELETE/api/admin/users/:id/roles/:roleIdDetach a role.
POST/api/admin/users/:id/bonus-pointsAdd or subtract bonus points with an audit-logged reason.
GET/api/admin/rolesRole catalogue.
POST/api/admin/rolesCreate a role.
PUT/api/admin/roles/:idEdit role (name, permissions, auto-assignment rules).
DELETE/api/admin/roles/:idRemove a role.
POST/api/admin/roles/recomputeRe-evaluate every user against auto-assignment rules.

See Roles & Permissions.

Settings, branding, system ​

MethodPathPurpose
GET/api/admin/settingsAll site settings (title, tagline, force-2FA scope, retention, request auto-validate timeout, request fill cap, …).
PUT/api/admin/settingsSave settings (any subset; missing keys are left untouched).
POST/api/admin/logoUpload the site logo (multipart).
POST/api/admin/faviconUpload the favicon (multipart).
GET/api/admin/statsLive aggregate stats (users, torrents, peers, DB size).
GET/api/admin/stats/historyTime-series for the admin charts.
GET/api/admin/system/versionCurrent version + commit + runtime label.
GET/api/admin/system/updateLatest release available on GitHub (queries TRACKARR_REPO).

See Branding.

Categories & tags ​

MethodPathPurpose
POST/api/admin/categoriesCreate a category.
PUT/api/admin/categories/:idEdit a category.
DELETE/api/admin/categories/:idRemove a category.
POST/api/admin/categories/seedSeed the default category tree (idempotent — skips existing rows).
POST/api/admin/categories/:id/mergeFold a category into another ({into}): torrents, sub-categories, saved searches, upload requests, upload-rule patterns and the federated map all follow, then the source is deleted. Refused when the two differ on the adult flag, when the target descends from the source, or when a category with children would land under a non-root.
POST/api/admin/tagsCreate a tag.
DELETE/api/admin/tags/:idDelete a tag (unbinds it from every torrent in the same transaction).

Search misses ​

What members looked for and the catalogue does not have. Recorded only for a text-only search, on the first page, that returned nothing — never who searched.

MethodPathPurpose
GET/api/admin/search-missesMost-asked first, with a count and when it was last asked.
DELETE/api/admin/search-missesClear one ({query}) or, with no body, the whole list.

Upload rules ​

MethodPathPurpose
GET/api/admin/upload-rulesRead rules (NFO requirement, description min length, regex patterns).
PUT/api/admin/upload-rulesUpdate rules (atomic).

See Upload Rules.

Bonus rules & events ​

MethodPathPurpose
GET/api/admin/bonus-rulesRead all rules + tier sets.
PATCH/api/admin/bonus-rules/rule/:kindConfigure a rule (seeding, first_seeder, daily_login, …).
POST/api/admin/bonus-rules/tiers/seed-countAdd a seeder-count tier.
PATCH/api/admin/bonus-rules/tiers/seed-count/:idEdit a seeder-count tier.
DELETE/api/admin/bonus-rules/tiers/seed-count/:idRemove a tier.
POST/api/admin/bonus-rules/tiers/ageAdd a torrent-age tier.
PATCH/api/admin/bonus-rules/tiers/age/:idEdit an age tier.
DELETE/api/admin/bonus-rules/tiers/age/:idRemove an age tier.
GET/api/admin/bonus-eventsList scheduled events.
POST/api/admin/bonus-eventsCreate a Freeleech / Silverleech / custom event.
PATCH/api/admin/bonus-events/:idEdit an event.
DELETE/api/admin/bonus-events/:idCancel an event.
POST/api/admin/bonus-events/:id/togglePause / resume a running event.
GET/api/admin/shop/itemsShop catalogue (admin view).
POST/api/admin/shop/itemsAdd a shop item.
PATCH/api/admin/shop/items/:idEdit a shop item.
DELETE/api/admin/shop/items/:idRemove a shop item.

HnR ​

MethodPathPurpose
GET/api/admin/hnrList violations.
PUT/api/admin/hnr/:idMark exempt / cleared / unresolved.

See Hit-and-Run.

Banned IPs ​

MethodPathPurpose
GET/api/admin/banned-ipsList banlist entries.
POST/api/admin/banned-ipsAdd an IP banlist row.
DELETE/api/admin/banned-ips/:ipRemove an IP banlist row.

Invitations ​

MethodPathPurpose
GET/api/admin/invitesEvery invite code + usage state.
POST/api/admin/invites/generateBatch-generate codes (operator share link).
POST/api/admin/invites/grantAward invite quota to a user.
DELETE/api/admin/invites/:idRevoke a code.

See Invitations.

Reports ​

MethodPathPurpose
GET/api/admin/reportsQueue (paginated, filterable by target_type and status).
PUT/api/admin/reports/:idResolve a report. For targetType='user' + status='resolved', accepts banDuration (none / 1d / 7d / 1m / 1y / permanent) and banReason to ban the offender in the same transaction. See Reports.

Notification channels (server-side config) ​

MethodPathPurpose
GET/api/admin/notification-channelsList configured channels + capability matrix.
PUT/api/admin/notification-channels/:typeConfigure a channel (SMTP host, Discord token, VAPID keys, …).
POST/api/admin/notification-channels/:type/testSend a test through this channel (uses the admin's own user as recipient).

Torznab admin ​

MethodPathPurpose
GET/api/admin/torznabTorznab config + per-user key inventory.
PUT/api/admin/torznabToggle Torznab + edit limits.
GET/api/admin/torznab/usersActive user keys.
POST/api/admin/torznab/users/:id/blockRevoke a user's Torznab key.
POST/api/admin/torznab/users/:id/resetRotate a user's Torznab key.
GET/api/admin/torznab/blacklistQuery patterns currently blacklisted.
POST/api/admin/torznab/unblockLift a blacklist entry.
GET/api/admin/torznab/logsRecent Torznab requests with status + handler.
GET/api/admin/torznab/statsAggregate request counters by handler.

Panic Mode ​

MethodPathPurpose
GET/api/admin/panicEncryption state, last-activation timestamp and phase (idle, encrypting, encrypted, restoring).
POST/api/admin/panic/encryptActivate. Body: { confirmation: "ENCRYPT_ALL_DATA" }.
POST/api/admin/panic/restoreRestore. Body: { password }.

See Panic Mode. Activation is irreversible without the original password — operators are warned in-app.

Debug ​

MethodPathPurpose
GET/api/admin/debug/tracker-statusRaw tracker health + peer counter snapshot.
GET/api/admin/debug/redis-peersPeer hash sample for one info_hash.
GET/api/admin/debug/redis-all-peersFull SCAN over peers:*. Heavy — use sparingly.

Rate limits ​

The API enforces sliding windows in Redis. Limits are tuned in apps/api/utils/rateLimit.ts and are not env-driven today.

BucketLimitWindow
Public read100 req1 min
Mutations (POST/PUT/PATCH/DELETE)10 req1 min
Auth (/api/auth/*)5 req5 min
Tracker announce200 req1 min

Exceeding a bucket returns 429 Too Many Requests. Repeated offences within a short window add a temporary IP ban — see Security.

Error shape ​

Every error response uses h3's default shape:

json
{
  "url": "/api/example",
  "statusCode": 429,
  "statusMessage": "Too Many Requests",
  "message": "Rate limit exceeded",
  "data": {}
}

Common status codes:

CodeMeaning
400Validation failed (Zod body / query schema).
401Not authenticated (no session cookie).
403Authenticated but lacking the required permission/role.
404Resource not found, OR (deliberate) hidden — see Moderation thread.
409Conflict (e.g. duplicate info_hash on upload, role-rule collision).
429Rate limited.
500Server error — surface to the operator via LOG_LEVEL=error.

Released under the MIT License.