Self-hosted Nostr indexer for NIP-35 torrent events with federated curation
Complete documentation for Lighthouse REST and Torznab APIs.
Most endpoints require an API key passed in the X-API-Key header:
curl -H "X-API-Key: your-api-key" http://localhost:9999/api/stats
The API key is configured in config.yaml or auto-generated on first run.
Base URL: http://localhost:9999/api
GET /api/stats
Returns dashboard statistics.
Response:
{
"total_torrents": 1234,
"total_events": 5678,
"trusted_publishers": 42,
"categories": {
"movies": 456,
"tv": 234,
"audio": 123
}
}
GET /api/search
Parameters:
| Name | Type | Description |
|---|---|---|
q |
string | Search query |
category |
integer | Torznab category code |
limit |
integer | Max results (default: 50) |
offset |
integer | Pagination offset |
Example:
curl "http://localhost:9999/api/search?q=ubuntu&category=4000"
Response:
{
"results": [
{
"id": "abc123",
"infohash": "aabbccdd...",
"name": "Ubuntu 24.04 Desktop",
"title": "Ubuntu 24.04 LTS",
"size": 4500000000,
"category": 4000,
"seeders": 1500,
"leechers": 50,
"created_at": "2024-04-01T00:00:00Z",
"magnet_uri": "magnet:?xt=urn:btih:..."
}
],
"total": 1
}
GET /api/torrents/{id}
Response:
{
"id": "abc123",
"infohash": "aabbccdd...",
"infohash_version": "v1",
"name": "Example Torrent",
"title": "Example Title",
"size": 1000000000,
"category": 2000,
"tags": ["linux", "distro"],
"magnet_uri": "magnet:?xt=urn:btih:...",
"curation_status": "accepted",
"created_at": "2024-01-01T00:00:00Z",
"event_id": "nostr_event_id",
"pubkey": "publisher_npub"
}
DELETE /api/torrents/{id}
Removes a torrent from the local index.
POST /api/publish/parse-torrent
Content-Type: multipart/form-data
Upload a .torrent file to extract metadata.
Request:
curl -X POST \
-F "file=@example.torrent" \
http://localhost:9999/api/publish/parse-torrent
Response:
{
"name": "Example File",
"infohash": "aabbccdd...",
"size": 1000000000,
"files": [
{"path": "file1.txt", "size": 500000000},
{"path": "file2.txt", "size": 500000000}
]
}
POST /api/publish
Content-Type: application/json
Request Body:
{
"name": "Example Torrent",
"infohash": "aabbccdd...",
"size": 1000000000,
"category": 2000,
"tags": ["linux"],
"trackers": ["udp://tracker.example.com:6969"]
}
Response:
{
"event_id": "nostr_event_id",
"relays_published": ["wss://relay.damus.io"]
}
GET /api/trust/whitelist
Response:
{
"entries": [
{
"npub": "npub1...",
"note": "Trusted uploader",
"added_at": "2024-01-01T00:00:00Z"
}
]
}
POST /api/trust/whitelist
Content-Type: application/json
Request Body:
{
"npub": "npub1...",
"note": "Trusted uploader"
}
DELETE /api/trust/whitelist/{npub}
Discovers a user’s preferred relays via NIP-65 and adds their write relays to your relay list.
POST /api/trust/whitelist/{npub}/discover-relays
Response:
{
"npub": "npub1...",
"relays_found": 5,
"relays_added": 3,
"message": "Discovered 5 relays, added 3 new relays"
}
Discovers relays for all whitelisted users.
POST /api/trust/whitelist/discover-all-relays
Response:
{
"users_processed": 10,
"total_relays_added": 8,
"results": [
{"npub": "npub1...", "relays_added": 3},
{"npub": "npub2...", "relays_added": 0, "error": "No NIP-65 relay list"}
]
}
GET /api/trust/blacklist
POST /api/trust/blacklist
Content-Type: application/json
Request Body:
{
"npub": "npub1...",
"reason": "Spam"
}
DELETE /api/trust/blacklist/{npub}
GET /api/trust/curators
Response:
{
"curators": [
{
"pubkey": "npub1...",
"name": "Movie Curator",
"weight": 1.0,
"added_at": "2024-01-01T00:00:00Z"
}
],
"aggregation_policy": {
"mode": "quorum",
"quorum_required": 2
}
}
POST /api/trust/curators
Content-Type: application/json
Request Body:
{
"pubkey": "npub1...",
"name": "Movie Curator",
"weight": 1.0
}
PUT /api/trust/curators/{pubkey}
Content-Type: application/json
Request Body:
{
"name": "Updated Name",
"weight": 2.0
}
DELETE /api/trust/curators/{pubkey}
PUT /api/trust/aggregation
Content-Type: application/json
Request Body:
{
"mode": "quorum",
"quorum_required": 2
}
Modes: any, all, quorum, weighted
GET /api/decisions
Parameters:
| Name | Type | Description |
|---|---|---|
status |
string | accept or reject |
curator |
string | Curator pubkey |
limit |
integer | Max results |
offset |
integer | Pagination |
Response:
{
"decisions": [
{
"decision_id": "abc123",
"decision": "accept",
"reason_codes": [],
"ruleset_type": "semantic",
"ruleset_version": "1.0.0",
"target_infohash": "aabbccdd...",
"curator_pubkey": "npub1...",
"created_at": "2024-01-01T00:00:00Z"
}
],
"total": 100
}
GET /api/decisions/{infohash}
GET /api/rulesets
Response:
{
"rulesets": [
{
"id": "abc123",
"name": "Default Censoring",
"type": "censoring",
"version": "1.0.0",
"hash": "sha256...",
"rules_count": 10
}
]
}
GET /api/rulesets/{id}
POST /api/rulesets
Content-Type: application/json
Request Body:
{
"name": "Custom Rules",
"type": "semantic",
"version": "1.0.0",
"rules": [
{
"id": "rule1",
"name": "Minimum Size",
"type": "threshold",
"field": "size",
"operator": "gte",
"value": 1000000,
"reason_code": "SEM_LOW_QUALITY"
}
]
}
POST /api/reports
Content-Type: application/json
Request Body:
{
"kind": "report",
"category": "dmca",
"target_infohash": "aabbccdd...",
"evidence": "Description of issue",
"jurisdiction": "US"
}
Categories: dmca, illegal, spam, malware, false_info, duplicate, other
GET /api/reports
Parameters:
| Name | Type | Description |
|---|---|---|
status |
string | pending, acknowledged, investigating, resolved, rejected |
category |
string | Filter by category |
GET /api/reports/pending
PUT /api/reports/{id}
Content-Type: application/json
Request Body:
{
"status": "resolved",
"resolution": "Content removed"
}
POST /api/reports/{id}/acknowledge
GET /api/torrents/{infohash}/comments
Response:
{
"comments": [
{
"id": "abc123",
"content": "Great release!",
"rating": 5,
"author_pubkey": "npub1...",
"created_at": "2024-01-01T00:00:00Z",
"replies": []
}
],
"stats": {
"total": 10,
"average_rating": 4.5
}
}
POST /api/torrents/{infohash}/comments
Content-Type: application/json
Request Body:
{
"content": "Great release!",
"rating": 5
}
GET /api/comments/recent
GET /api/relays
Response:
{
"relays": [
{
"url": "wss://relay.damus.io",
"name": "Damus",
"preset": "public",
"enabled": true,
"connected": true,
"last_event": "2024-01-01T00:00:00Z"
}
]
}
POST /api/relays
Content-Type: application/json
Request Body:
{
"url": "wss://relay.example.com",
"name": "My Relay",
"preset": "private"
}
PUT /api/relays/{url}
Content-Type: application/json
Request Body:
{
"enabled": false
}
DELETE /api/relays/{url}
GET /api/settings
PUT /api/settings
Content-Type: application/json
Request Body:
{
"trust.depth": 1,
"enrichment.tmdb_api_key": "your-key"
}
GET /api/settings/identity
POST /api/settings/identity/generate
POST /api/settings/identity/import
Content-Type: application/json
Request Body:
{
"nsec": "nsec1..."
}
GET /api/indexer/status
Response:
{
"running": true,
"events_processed": 12345,
"last_event": "2024-01-01T00:00:00Z"
}
POST /api/indexer/start
POST /api/indexer/stop
Fetches historical torrent events from trusted uploaders. By default fetches all events (no time limit).
POST /api/indexer/resync
Parameters:
| Name | Type | Description |
|---|---|---|
days |
integer | Limit to last N days (default: 0 = no limit) |
Example:
# Fetch all historical events
curl -X POST http://localhost:9999/api/indexer/resync
# Fetch last 7 days only
curl -X POST "http://localhost:9999/api/indexer/resync?days=7"
Response:
{
"status": "syncing",
"message": "Fetching historical torrents",
"days": 0
}
Torznab is a standardized API for torrent indexers, compatible with Prowlarr, Sonarr, Radarr, and other *arr applications.
Base URL: http://localhost:9999/api/torznab
Pass API key as query parameter:
/api/torznab?apikey=your-api-key&t=search&q=query
GET /api/torznab?t=caps
Returns XML describing supported features and categories.
GET /api/torznab?t=search&q={query}
Parameters:
| Name | Type | Description |
|---|---|---|
q |
string | Search query |
cat |
string | Category IDs (comma-separated) |
limit |
integer | Max results (default: 100) |
offset |
integer | Pagination offset |
GET /api/torznab?t=tvsearch
Parameters:
| Name | Type | Description |
|---|---|---|
q |
string | Search query |
season |
string | Season number |
ep |
string | Episode number |
tvdbid |
string | TVDB ID |
imdbid |
string | IMDB ID |
GET /api/torznab?t=movie
Parameters:
| Name | Type | Description |
|---|---|---|
q |
string | Search query |
imdbid |
string | IMDB ID |
tmdbid |
string | TMDB ID |
| ID | Category |
|---|---|
| 2000 | Movies |
| 2010 | Movies/Foreign |
| 2020 | Movies/Other |
| 2030 | Movies/SD |
| 2040 | Movies/HD |
| 2045 | Movies/UHD |
| 2050 | Movies/BluRay |
| 2060 | Movies/3D |
| 2070 | Movies/DVD |
| 2080 | Movies/WEB-DL |
| 5000 | TV |
| 5010 | TV/WEB-DL |
| 5020 | TV/Foreign |
| 5030 | TV/SD |
| 5040 | TV/HD |
| 5045 | TV/UHD |
| 5050 | TV/Other |
| 5060 | TV/Sport |
| 5070 | TV/Anime |
| 5080 | TV/Documentary |
| 3000 | Audio |
| 3010 | Audio/MP3 |
| 3020 | Audio/Video |
| 3030 | Audio/Audiobook |
| 3040 | Audio/Lossless |
| 4000 | PC |
| 4010 | PC/0day |
| 4020 | PC/ISO |
| 4030 | PC/Mac |
| 4040 | PC/Mobile-Other |
| 4050 | PC/Games |
| 4060 | PC/Mobile-iOS |
| 4070 | PC/Mobile-Android |
| 7000 | Books |
| 7010 | Books/Mags |
| 7020 | Books/EBook |
| 7030 | Books/Comics |
| 7040 | Books/Technical |
| 7050 | Books/Other |
| 7060 | Books/Foreign |
| 6000 | XXX |
All endpoints return consistent error format:
{
"error": "Error message",
"code": "ERROR_CODE"
}
| Code | Description |
|---|---|
| 200 | Success |
| 201 | Created |
| 400 | Bad Request |
| 401 | Unauthorized (invalid/missing API key) |
| 404 | Not Found |
| 500 | Internal Server Error |
Default limits:
Exceeded limits return 429 Too Many Requests.