File API
Every operation is plain HTTPS. Authenticated uploads and management, public downloads through UUID links.
Base URL
All endpoints live under https://sturmv1.lol/api/v1. Successful responses wrap the result in { data, requestId }. Failures use application/problem+json with a code and the requestId — include that ID if you ask for help.
GET /api/v1/health, GET /api/v1/capabilities, and GET /api/v1/openapi.json need no credentials. Check the capabilities before uploading.
Authentication
Each management request carries five headers plus the file headers for uploads:
X-Access-Key: <access-key>
X-Timestamp: <unix-seconds>
X-Nonce: <random-nonce>
X-Content-SHA256: <body-sha256>
Authorization: Sturm-HMAC-SHA256 <hex-signature>The timestamp must be within 300 seconds of the server clock. The nonce is fresh per request: 16–128 characters of [A-Za-z0-9_-]; reuse is rejected. The digest is the hex SHA-256 of the exact body bytes, including the empty body on requests without one.
The signature is HMAC-SHA256(secretKey, canonical), where the canonical string is these sixteen lines in order:
STURM-HMAC-SHA256
<METHOD>
<path>
<canonical-query>
<host>
<x-access-key>
<x-timestamp>
<x-nonce>
<x-content-sha256>
<content-type-lowercased>
<x-file-name>
<x-file-mime>
<x-file-size>
<x-file-expires-at>
<idempotency-key>
<if-match>Absent headers sign as empty lines. The method is uppercased, the query is RFC 3986 encoded and sorted, and the host is lowercased. The Secret Key never travels in a URL or a body — only the signature does. When retrying, generate a new nonce and keep the same Idempotency-Key.
Upload
curl -X POST https://sturmv1.lol/api/v1/files \
--data-binary @./file.txt \
-H "Content-Type: application/octet-stream" \
-H "X-Access-Key: <access-key>" \
-H "X-Timestamp: <unix-seconds>" \
-H "X-Nonce: <random-nonce>" \
-H "X-Content-SHA256: <body-sha256>" \
-H "Authorization: Sturm-HMAC-SHA256 <hex-signature>" \
-H "X-File-Name: file.txt" \
-H "X-File-Mime: text/plain" \
-H "X-File-Size: 1234" \
-H "Idempotency-Key: <unique-key>"X-File-Name is required and percent-encoded; X-File-Mime defaults to application/octet-stream. X-File-Size must match the body exactly, and X-File-Expires-At takes an ISO date within the retention policy. Idempotency-Key needs 16–128 characters of [A-Za-z0-9_-]: the same key with the same bytes replays safely, while the same key with different bytes is rejected.
A successful upload returns 201 with the file metadata, including its id and public url.
Files
GET /api/v1/files?limit=25 List my files
GET /api/v1/files/<uuid> My file metadata
GET /api/v1/files/<uuid>/content My file bytes
PATCH /api/v1/files/<uuid> Update metadata
DELETE /api/v1/files/<uuid> Delete fileListing accepts limit (1–100), cursor, and status, and answers with { data, page: { limit, nextCursor } }. Pass the cursor back as ?cursor= until nextCursor is null.
curl https://sturmv1.lol/api/v1/files?limit=25 \
-H "X-Access-Key: <access-key>" \
-H "X-Timestamp: <unix-seconds>" \
-H "X-Nonce: <random-nonce>" \
-H "X-Content-SHA256: <empty-body-sha256>" \
-H "Authorization: Sturm-HMAC-SHA256 <hex-signature>"Metadata updates send JSON with If-Match holding the current quoted version:
curl -X PATCH https://sturmv1.lol/api/v1/files/<uuid> \
-d '{"name": "report-final.pdf"}' \
-H "Content-Type: application/json" \
-H "X-Access-Key: <access-key>" \
-H "X-Timestamp: <unix-seconds>" \
-H "X-Nonce: <random-nonce>" \
-H "X-Content-SHA256: <body-sha256>" \
-H "Authorization: Sturm-HMAC-SHA256 <hex-signature>" \
-H 'If-Match: "1"'A stale version answers 412 VERSION_CONFLICT. Deletion answers 202 with { id, status: "deleting", jobId }; the bytes are removed asynchronously.
Account
GET /api/v1/me My account and quota
GET /api/v1/me/usage Used, reserved, and available bytesDownload
curl --fail --output file.txt https://sturmv1.lol/f/<uuid>Direct public downloads live at GET /f/:uuid — no authentication, cookies, or tokens. The file must be available and unexpired. The link returns the original bytes directly, HEAD returns the headers only, and If-None-Match with the current ETag answers 304.
Rotation and errors
The Revocation Key is used exclusively to replace Access + Secret and is sent as a Bearer token only to the rotation endpoint, with an empty body and an Idempotency-Key. Existing links are preserved.
curl -X POST https://sturmv1.lol/api/v1/credentials/rotate \
-H "Authorization: Bearer <revocation-key>" \
-H "Idempotency-Key: <unique-key>"Each account allows up to 3 rotations. A 4th rotation request is refused with code ROTATION_LIMIT_REACHED and leaves the keys unchanged; a further rotation request permanently deletes the account with code ACCOUNT_DELETED.
Open the OpenAPI specification for contracts, parameters, headers, and responses.