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.
https://cloud.fortresshub.net/api (all examples below use it)Authentication
There are two supported auth methods. Pick whichever fits your client.
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.
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.
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/rootmeans "My Files" (the top level). - Mutating requests sent with session cookies must echo the
fortress_csrfcookie value in anX-CSRF-Tokenheader. 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
Create an account. Returns the new user, storage info and the account's API key.
| Field | Type | Notes |
|---|---|---|
| username | string | 3–32 chars, letters/digits/._- |
| string | Valid email address | |
| password | string | ≥8 chars, letters and numbers |
| confirmPassword | string | Must 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"}'Sign in with email (or username) + password. Returns the user, storage info and API key. Useful for an Android app's first launch.
Fetch the current account's profile and storage. A quick way to validate a stored API key.
Files
List files for the current account. Supports query filters:
| Query | Type | Notes |
|---|---|---|
| folderId | int | Restrict to a folder (default: root) |
| search | string | Name search across all files |
| favorite | bool | true / false |
| deleted | bool | true lists trash |
| sort | string | name | created | size |
| direction | string | asc | desc |
| limit | int | 1–200 |
curl -H "Authorization: Bearer fc_..." "https://cloud.fortresshub.net/api/files?folderId=3&sort=created&direction=desc"
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": {…}}.
Stream the original file bytes. Respects Content-Disposition so the browser names the download correctly.
Image preview (only for jpeg/png/gif/webp/svg/bmp/avif). Renders the raw image.
Update metadata. Accepts any subset of:
| Field | Type | Notes |
|---|---|---|
| newName | string | New file name |
| folderId | int | Move to a folder (null moves to root) |
| isFavorite | bool | Toggle 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}'Soft-delete to Trash. Undo with POST /api/files/:id/restore. Permanently erase with DELETE /api/files/:id/permanent.
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).
Create a folder. Body: {"name": "Documents", "parentId": 2} (parentId optional — defaults to root).
Rename ({"name": …}) and/or move ({"parentId": …}). Moving into your own descendant is rejected.
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
Quota details: limit, used, remaining, limitMb, remainingMb, percent, fileCount.
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_…"}}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")
}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/permanentError codes
UNAUTHORIZED401 — missing/invalid session or API keyINVALID_CREDENTIALS401 — wrong email/username or passwordFORBIDDEN403 — CSRF failure or action not allowedNOT_FOUND404 — item missing or owned by another accountINVALID_INPUT400 — validation failed (see details.field)CONFLICT409 — duplicate username/emailSTORAGE_LIMIT_EXCEEDED413 — quota fullRATE_LIMITED429 — too many auth/upload requestsLimits
- 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.