Upload API

Everything enters Fotolane through a single endpoint: /api/uploadFile/. It accepts whole files and resumable chunks over the same path.

Authentication

Two equivalent ways to present a project token.

MethodWhereNotes
Authorization Request header Preferred for server-to-server calls. Format: Bearer <token>.
auth Query parameter For browser uploads and any client that cannot set headers freely, pass ?auth=<token>. Tokens presented this way are short-lived and scoped to a single upload session.

A request without a recognised token is answered with 401 and an empty body — the endpoint deliberately does not describe why a token was rejected.

Whole-file upload

Simplest form. Suitable up to roughly 64 MB per request.

POST /api/uploadFile/ HTTP/1.1
Host: img.vikmen.run
Authorization: Bearer <token>
Content-Type: multipart/form-data; boundary=----fl

------fl
Content-Disposition: form-data; name="file"; filename="porch-light.jpg"
Content-Type: image/jpeg

<binary>
------fl--

Chunked upload

For large originals, flaky mobile links, or anything you want to resume. Open a session, then send parts in any order.

ParameterTypeMeaning
authstringSession token returned when the upload session is opened.
chunk_idintegerZero-based index of the part being sent. Parts may arrive out of order.
totalintegerOptional. Total number of parts, so the server can finalise without a separate call.
# send part 7 of an open session
curl -X POST "https://img.vikmen.run/api/uploadFile/?auth=$SID&chunk_id=7" \
  --data-binary @part-07.bin

# poll session state (GET on the same path)
curl "https://img.vikmen.run/api/uploadFile/?auth=$SID"

Sessions stay open for 30 minutes after the last accepted part. Parts are held in edge storage and are never cached: the endpoint always answers with Cache-Control: no-store.

Part integrity

Every chunked request carries a signature of its own payload.

HeaderValue
X-Part-Checksum Checksum of the part body, salted with the session token. Length varies with the negotiated digest; clients that cannot compute it may omit the header, in which case the part is accepted but not verified until the session is finalised.

The signature is recomputed per request, so it differs for every part even when the same bytes are re-sent after a retry.

Browser clients (CORS)

The endpoint answers preflight requests, so uploads can run straight from a page.

OPTIONS /api/uploadFile/ HTTP/1.1

<- 204 No Content
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type, X-Part-Checksum
Access-Control-Max-Age: 86400

Because a browser cannot attach an Authorization header to a form post without dropping into fetch(), browser uploads normally pass the session token as ?auth= instead. Both paths are equivalent server-side.

Delivery and transforms

Once stored, an object is addressed as /<id>/<transform>.<ext>.

ParameterExampleMeaning
w, hw_640Target width or height in pixels.
fitfit_coverOne of cover, contain, pad.
arar_16:9Aspect ratio; combined with fit_cover it crops.
qq_78Encoder quality, 1–100. Defaults to 82.
extension.avifjpg, png, webp, avif. Omit to keep the original.

Progressive delivery

Large originals and gallery prefetches are streamed in ordered segments instead of one long response, so a slow client can start rendering early and resume after a dropped connection.

PathMeaning
/api/v2/streams/<session>/<seq> Segment seq of an open delivery session. Sessions are opened by the player or gallery component; segment numbering starts at zero.
/api/v2/streams/<session> Session manifest: segment count, byte ranges and the content type being served.
/api/v2/segment.bin?auth=<session>&chunk_id=<seq> The same segment addressed by query instead of path, for players and proxies that only accept a fixed file URL. Without chunk_id the session is returned as one continuous response. A request without auth gets 401.
# fetch the third segment of an open session
curl "https://cdn.vikmen.run/api/v2/streams/$SID/2"

Path-addressed segments are served from the delivery domain; the query form is answered on both domains. Either way segments carry Cache-Control: no-store: the session is per-viewer and must not be shared between them. Unlike transform URLs, a stream session expires once the object has been fully delivered.

Status codes

CodeWhen
200Part accepted, or session state returned.
201Upload finalised; body carries the object descriptor.
400Malformed request: missing part index, unreadable body, unsupported media type.
401Token absent, expired, or not valid for this project.
413Part exceeds the per-request limit for your plan.
429Too many open sessions. Retry after the interval in Retry-After.