(Tighten AllowedOrigins to your own URL later if you like.)
API
One endpoint, chosen by ?action=. Base URL:
PUT the file as the body. The server streams it to R2 in ~16 MB parts, so even big files use little memory. name may include folders, e.g. clips/2026/a.mp4.
POST ?action=init {filename, content_type, parts} -> {upload_id, key, mode, part_urls[]}
PUT ?action=upload&name=PATH (raw body) -> {ok, key, parts} # one-shot
POST ?action=complete {key, upload_id, parts[]} -> {ok, key}
POST ?action=abort {key, upload_id} -> {ok}
GET/POST ?action=get_settings | save_settings
The web UI uses init → direct-to-R2 part PUTs → complete for max speed & huge files. upload is the simple one-request option.