Everything the web page does, over plain HTTP. No keys, no rate limits, no signup. Every endpoint returns JSON unless it returns bytes.
curl -T ./movie.mkv https://files.nitaimaarek.com/
curl --data-binary @dump.log https://files.nitaimaarek.com/dump.log
curl -F file=@photo.jpg https://files.nitaimaarek.com/api/form
The response is JSON: {"id":"aB3xY9k","url":"…/f/aB3xY9k","download":"…/d/aB3xY9k","token":"…"}.
Add ?raw=1 to get just the URL as text — handy in shell pipelines.
| expires | 0 · 1h · 1d · 7d · 30d · or seconds |
|---|---|
| listed | 0 keeps it off the public Browse page |
| pass | password required to download |
| note | short description shown on the file page |
| name | override the stored filename |
What the browser uses. Survives a dropped connection at any size.
POST /api/new {"name":"x.iso","size":123,"expires":0,"pass":"","listed":true}
-> {"id":"…","token":"…","offset":0}
PUT /api/put/<id>?token=…&offset=N raw bytes appended at N
GET /api/status/<id>?token=… -> {"offset":N} (where to resume)
POST /api/done/<id>?token=… -> the finished file's JSON
curl -X POST -d '{"magnet":"magnet:?xt=urn:btih:…"}' https://files.nitaimaarek.com/api/torrent
curl -X POST -d '{"url":"https://…/thing.torrent"}' https://files.nitaimaarek.com/api/torrent
curl -X POST --data-binary @thing.torrent https://files.nitaimaarek.com/api/torrent
All three answer {"job":"…"} and are polled at /api/import/<job> like a URL
import. A magnet pasted into /api/import is routed here too. Every file inside the torrent
becomes a normal stored file with its own id and delete token; the job's files array lists
them all. Upload options (expires, listed, pass, note)
work as query parameters and apply to each one.
Limits: 9 GB and 200 files per torrent, and it still has to fit in the free space. A .torrent is measured before anything is fetched; a magnet the moment its metadata arrives. An import gives up after 10 minutes with no peers, or 30 minutes without progress. The box does not keep seeding what it pulled — the stored copy gets its own torrent, and that is what it seeds.
POST /api/import {"url":"https://…","expires":"7d"} -> {"job":"…"}
GET /api/import/<job> -> {"state":"running","got":123,"total":456}
The transfer runs on the server, so closing the tab does not stop it.
curl https://files.nitaimaarek.com/list
curl 'https://files.nitaimaarek.com/list?page=3'
curl 'https://files.nitaimaarek.com/list?kind=video&per=50'
Plain text, ten public files a page, newest first — an id, a size, an age and a name per line.
?page=N walks back through the rest; a page past the end comes back empty rather than
failing, so a loop just stops. The same filters as the Browse page apply
(q, kind, sort), ?per=N sets the page size up to
500, and ?format=json returns the JSON shape instead.
Any file that is not password-protected can be handed to a torrent client:
curl https://files.nitaimaarek.com/m/<id> # the magnet URI, one line
curl -O https://files.nitaimaarek.com/t/<id> # the .torrent file
The .torrent carries this server as a web seed (BEP 19), so a client pulls the bytes straight over HTTP with nobody else in the swarm — and anyone who has the file then seeds it to the next person. Public trackers are listed so a swarm can form. Hashing happens once, on the first request, and is cached from then on; the first call on a very large file takes a while.
A magnet holds no metadata of its own — a client has to get that from a peer — so this box runs
a seed for every torrent it hands out, announcing to the trackers above and on the DHT. That is what
makes a magnet work from cold, with no web seed involved. The magnet also carries ws,
xs and as as HTTP fallbacks.
Public files are hashed in the background, so the magnet is ready before anyone asks. An unlisted file is hashed the first time someone asks for its magnet, and joins the swarm at that point. Password-protected files are never hashed, seeded or announced. Two uploads of identical bytes under the same name are one torrent — same infohash, same swarm.
| GET /m/<id> | magnet URI as text/plain |
|---|---|
| GET /t/<id> | the .torrent file |
| GET /api/magnet/<id> | JSON: magnet, infohash, webseed, trackers, piece_length |
| GET /d/<id>/<name> | the bytes under a URL ending in the filename — this is the web seed |
| GET /d/<id> | attachment, honours Range — resume with curl -C - |
|---|---|
| GET /r/<id> | inline with the real content type, for hotlinking and embeds |
| GET /api/zip?ids=a,b,c | streams one zip64 archive of several files, with a real Content-Length |
| GET /api/file/<id> | metadata as JSON |
| GET /list?page= | public listing as plain text, ten a page — for curl |
| GET /api/list?q=&kind=&sort=&limit=&page= | public listing as JSON, with page, pages and a next link |
| GET /api/stats | counts, bytes stored, free space |
Password-protected files take ?pass=… on any of those.
POST /api/delete/<id>?token=<token from upload>
The token is the only way to delete a file, and it is only ever shown to the uploader. Lose it and the file stays until it expires.
No cap on a single file, no cap on how many, no throttling. The one hard rule is that
uploads stop while the disk has less than 3 GB free, so the box never fills up under the
other services running on it. Check /api/stats for current headroom.