Fortress Cloud API

Build anything on Fortress Cloud.

One REST API powers the web app, your Android client and your scripts. Every endpoint accepts both session cookies and a per-account API key.

Overview

Fortress Cloud exposes a JSON REST API under /api. All requests go over HTTPS. Requests and responses use the UTF-8 JSON encoding. The web UI is built on exactly these endpoints, so anything you can do in the browser you can do programmatically.

Base URLhttps://cloud.fortresshub.net/api (all examples below use it)

Authentication

There are two supported auth methods. Pick whichever fits your client.

WEBSession cookies (browser only)

Signing in sets a secure sid cookie. Mutations must also echo the fortress_csrf cookie value in an X-CSRF-Token header. Use this only in the browser — native apps should use an API key.

APPAPI key (apps, scripts, CLI)

Every account has a permanent key starting with fc_. Send it in an Authorization: Bearer header (or X-API-Key). API-key requests need no cookies and no CSRF token — this is the way native apps talk to every authenticated endpoint.

curl -H "Authorization: Bearer fc_..." https://cloud.fortresshub.net/api/files

Find your key under Settings → API key, or grab it from the register/login response (field apiKey). Regenerating a key invalidates the old one immediately.

Register & login only — these two endpoints are the one exception: they are unauthenticated mutations, so even a native client must first fetch the CSRF cookie and echo it. Do a GET /api/health, read the fortress_csrf cookie from the response, then send it back in an X-CSRF-Token header on the register/login request. After that, switch to your API key for everything else. See the Android example below.

Conventions

  • Success responses use the envelope {"success": true, "data": {…}}.
  • Errors use {"success": false, "error": "CODE", "message": "…"} with an appropriate HTTP status.
  • Dates are ISO-8601 strings; file sizes are integers in bytes.
  • IDs are integers; a folder or file id of 0/root means "My Files" (the top level).
  • Mutating requests sent with session cookies must echo the fortress_csrf cookie value in an X-CSRF-Token header. Requests authenticated with an API key (or the two register/login calls) are exempt.
  • Multipart uploads are the only non-JSON request; everything else is JSON with Content-Type: application/json.

Auth

POST/api/auth/register

Create an account. Returns the new user, storage info and the account's API key.

FieldTypeNotes
usernamestring3–32 chars, letters/digits/._-
emailstringValid email address
passwordstring≥8 chars, letters and numbers
confirmPasswordstringMust match password
curl -X POST https://cloud.fortresshub.net/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","email":"alice@example.com","password":"Secret123","confirmPassword":"Secret123"}'
POST/api/auth/login

Sign in with email (or username) + password. Returns the user, storage info and API key. Useful for an Android app's first launch.

GET/api/auth/me

Fetch the current account's profile and storage. A quick way to validate a stored API key.

Files

GET/api/files

List files for the current account. Supports query filters:

QueryTypeNotes
folderIdintRestrict to a folder (default: root)
searchstringName search across all files
favoritebooltrue / false
deletedbooltrue lists trash
sortstringname | created | size
directionstringasc | desc
limitint1–200
curl -H "Authorization: Bearer fc_..." "https://cloud.fortresshub.net/api/files?folderId=3&sort=created&direction=desc"
POST/api/files/upload

Upload a file (multipart/form-data). Field file holds the binary; optional folderId targets a folder. Server-measured size is used for quota, so clients can't cheat.

curl -X POST https://cloud.fortresshub.net/api/files/upload \
  -H "Authorization: Bearer fc_..." \
  -F "file=@photo.jpg" \
  -F "folderId=5"

Returns 201 with {"file": {…}, "storage": {…}}.

GET/api/files/:id/download

Stream the original file bytes. Respects Content-Disposition so the browser names the download correctly.

GET/api/files/:id/thumbnail

Image preview (only for jpeg/png/gif/webp/svg/bmp/avif). Renders the raw image.

PATCH/api/files/:id

Update metadata. Accepts any subset of:

FieldTypeNotes
newNamestringNew file name
folderIdintMove to a folder (null moves to root)
isFavoriteboolToggle favorite
curl -X PATCH https://cloud.fortresshub.net/api/files/42 \
  -H "Authorization: Bearer fc_..." -H "Content-Type: application/json" \
  -d '{"newName":"vacation.jpg","isFavorite":true}'
DELETE/api/files/:id

Soft-delete to Trash. Undo with POST /api/files/:id/restore. Permanently erase with DELETE /api/files/:id/permanent.

Folders

GET/api/folders

List folders. Filters: parentId, search, deleted. Also see GET /api/folders/tree (all folders flat, handy for move pickers) and GET /api/folders/:id/path (breadcrumb path).

POST/api/folders

Create a folder. Body: {"name": "Documents", "parentId": 2} (parentId optional — defaults to root).

PATCH/api/folders/:id

Rename ({"name": …}) and/or move ({"parentId": …}). Moving into your own descendant is rejected.

DELETE/api/folders/:id

Soft-delete the whole subtree to Trash. Restore via POST /api/folders/:id/restore (restores children too). Permanently delete with DELETE /api/folders/:id/permanent.

Account

GET/api/storage

Quota details: limit, used, remaining, limitMb, remainingMb, percent, fileCount.

GET/api/settings/apikey

Return the account's current API key (generates one on first access for legacy accounts).

curl -H "Authorization: Bearer fc_..." https://cloud.fortresshub.net/api/settings/apikey
# → {"success":true,"data":{"apiKey":"fc_…"}}
POST/api/settings/apikey/rotate

Generate a new API key and invalidate the old one immediately. Any app using the previous key must be updated.

Android quick start

Grab your API key from Settings → API key (or the login response) and send it on every request. No cookies, no CSRF. This dependency-free Kotlin example uses HttpURLConnection:

class FortressCloud(private val apiKey: String) {

    private val base = "https://cloud.fortresshub.net"

    private fun call(path: String, method: String, body: String? = null): String {
        val conn = URL(base + path).openConnection() as HttpURLConnection
        conn.requestMethod = method
        conn.setRequestProperty("Authorization", "Bearer $apiKey")
        conn.connectTimeout = 15_000
        conn.readTimeout = 30_000
        if (body != null) {
            conn.doOutput = true
            conn.setRequestProperty("Content-Type", "application/json")
            conn.outputStream.use { it.write(body.toByteArray()) }
        }
        val status = conn.responseCode
        val raw = (if (status in 200..299) conn.inputStream else conn.errorStream)
            .bufferedReader().readText()
        conn.disconnect()
        val json = JSONObject(raw)
        if (!json.getBoolean("success")) throw IOException(json.optString("message"))
        return json.optString("data")
    }

    fun listFiles(folderId: Long? = null): JSONArray {
        val q = folderId?.let { "?folderId=$it" } ?: ""
        return JSONObject(call("/api/files$q", "GET")).getJSONArray("files")
    }

    fun upload(file: File, folderId: Long? = null) {
        val boundary = "----Fortress${System.currentTimeMillis()}"
        val conn = URL(base + "/api/files/upload").openConnection() as HttpURLConnection
        conn.requestMethod = "POST"
        conn.setRequestProperty("Authorization", "Bearer $apiKey")
        conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=$boundary")
        conn.doOutput = true
        val out = conn.outputStream.bufferedWriter()
        out.write("--$boundary\r\n")
        out.write("Content-Disposition: form-data; name=\"file\"; filename=\"${file.name}\"\r\n\r\n")
        out.flush()
        file.inputStream().use { it.copyTo(conn.outputStream) }
        out.write("\r\n")
        if (folderId != null) {
            out.write("--$boundary\r\nContent-Disposition: form-data; name=\"folderId\"\r\n\r\n$folderId\r\n")
        }
        out.write("--$boundary--\r\n")
        out.flush(); out.close()
        check(conn.responseCode == 201) { "Upload failed: ${conn.responseCode}" }
        conn.disconnect()
    }
}

Registering and logging in (CSRF dance)

Only the very first steps need cookies. Fetch the CSRF token from a GET /api/health, then send it back on register/login. Once you have the apiKey from the response, use Bearer auth for everything else.

fun fetchCsrfToken(): String {
    // Disable any global cookie jar so the raw Set-Cookie header is visible.
    val conn = URL(base + "/api/health").openConnection() as HttpURLConnection
    conn.requestMethod = "GET"
    conn.inputStream.close()
    val setCookie = conn.headerFields["Set-Cookie"]
        ?.firstOrNull { it.startsWith("fortress_csrf=") }
        ?: throw IOException("Server did not issue a CSRF cookie")
    conn.disconnect()
    return setCookie.substringAfter("fortress_csrf=").substringBefore(";")
}

fun login(email: String, password: String): String {
    val token = fetchCsrfToken()
    val conn = URL(base + "/api/auth/login").openConnection() as HttpURLConnection
    conn.requestMethod = "POST"
    conn.doOutput = true
    conn.setRequestProperty("Content-Type", "application/json")
    conn.setRequestProperty("X-CSRF-Token", token)
    conn.outputStream.use {
        it.write("""{"email":"$email","password":"$password"}""".toByteArray())
    }
    val json = JSONObject(conn.inputStream.bufferedReader().readText())
    conn.disconnect()
    if (!json.getBoolean("success")) throw IOException(json.optString("message"))
    return json.getJSONObject("data").optString("apiKey") // → use as your FortressCloud(apiKey)
}

fun register(username: String, email: String, password: String): String {
    val token = fetchCsrfToken()
    val conn = URL(base + "/api/auth/register").openConnection() as HttpURLConnection
    conn.requestMethod = "POST"
    conn.doOutput = true
    conn.setRequestProperty("Content-Type", "application/json")
    conn.setRequestProperty("X-CSRF-Token", token)
    conn.outputStream.use {
        val body = """{"username":"$username","email":"$email","password":"$password","confirmPassword":"$password"}"""
        it.write(body.toByteArray())
    }
    val json = JSONObject(conn.inputStream.bufferedReader().readText())
    conn.disconnect()
    if (!json.getBoolean("success")) throw IOException(json.optString("message"))
    return json.getJSONObject("data").optString("apiKey")
}
Certificates — this instance serves a valid Let's Encrypt certificate (renewed automatically), so HTTPS works out of the box on stock Android, iOS and desktop clients. No certificate pinning or custom TrustManager is required.

curl quick reference

KEY="fc_your-api-key"
H="Authorization: Bearer $KEY"

# register/login from a script (CSRF dance):
#   first call issues the fortress_csrf cookie, then echo it back
J=/tmp/fortress-cookies.txt
curl -s -c "$J" https://cloud.fortresshub.net/api/health > /dev/null
CSRF=$(awk -F '\t' '$6=="fortress_csrf"{print $7}' "$J")
curl -s -b "$J" -c "$J" -X POST https://cloud.fortresshub.net/api/auth/login \
     -H "Content-Type: application/json" -H "X-CSRF-Token: $CSRF" \
     -d '{"email":"alice@example.com","password":"Secret123"}' \
     | jq -r .data.apiKey   # your permanent key

# storage + profile
curl -H "$H" https://cloud.fortresshub.net/api/auth/me
curl -H "$H" https://cloud.fortresshub.net/api/storage

# files & folders
curl -H "$H" "https://cloud.fortresshub.net/api/files?search=report"
curl -H "$H" https://cloud.fortresshub.net/api/folders/tree
curl -X POST -H "$H" -H "Content-Type: application/json" \
     -d '{"name":"Reports"}' https://cloud.fortresshub.net/api/folders
curl -X POST -H "$H" -F "file=@report.pdf" -F "folderId=1" \
     https://cloud.fortresshub.net/api/files/upload
curl -H "$H" -OJ https://cloud.fortresshub.net/api/files/12/download
curl -X PATCH -H "$H" -H "Content-Type: application/json" \
     -d '{"newName":"final.pdf","isFavorite":true}' https://cloud.fortresshub.net/api/files/12

# trash
curl -X DELETE -H "$H" https://cloud.fortresshub.net/api/files/12
curl -X POST   -H "$H" https://cloud.fortresshub.net/api/files/12/restore
curl -X DELETE -H "$H" https://cloud.fortresshub.net/api/files/12/permanent

Error codes

UNAUTHORIZED401 — missing/invalid session or API key
INVALID_CREDENTIALS401 — wrong email/username or password
FORBIDDEN403 — CSRF failure or action not allowed
NOT_FOUND404 — item missing or owned by another account
INVALID_INPUT400 — validation failed (see details.field)
CONFLICT409 — duplicate username/email
STORAGE_LIMIT_EXCEEDED413 — quota full
RATE_LIMITED429 — too many auth/upload requests

Limits

  • 10 MB storage per account, enforced server-side on every upload.
  • Auth endpoints: 30 requests per 15 minutes per IP.
  • Uploads: 120 per minute per IP.
  • Listings cap at 200 items per request.
Security — treat your API key like a password. It grants full read/write access to your account. Rotate it from Settings → API key if it ever leaks.