Skip to content
Back to Sturm

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 file

Listing 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 bytes

Download

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.