Nuvaka › Developer docs › Project API
Project API
Your project site's or service's server keeps its files on Nuvaka: it uploads them, lists them, downloads them back when needed and gives its visitors public download links. Authentication is a single server-side project key (nvk_…); there is no user sign-in and no browser flow.
What a project key is
- A project key grants access to one root folder in a Nuvaka account's My Files (for example
Projeler/cyd). Every path is relative to that root; the key can never reach outside it, other folders or the Secure Area. - Keys are issued only by Nuvaka admins; you cannot create one yourself. A key is defined with its root folder, scopes, an optional folder quota and an optional IP allow-list. The key is shown only once; Nuvaka stores only its hash.
- If you are building an app where users sign in with their own accounts, you want Sign in with Nuvaka (OAuth,
storage.folder) instead.
The key is a server-side secret
- Keep the key on your project's own server only: an environment variable or a file with restricted permissions. Never put it in JavaScript sent to browsers, in a mobile app, in device firmware or in a git repository.
- That is why the API sends no CORS headers: it cannot be called directly from a browser. To give visitors a file, your server creates a share link or downloads the file and serves it itself.
- If the key leaks, ask an admin to rotate it: the old key stops working immediately. If your server has a fixed outbound IP, restricting the key to that IP is a good extra safeguard.
If a key is deleted
- A deleted key stops working immediately: every request gets
401 invalid_key. - For 30 days the share links the key created keep working, and the files in its root folder are never touched. During that time an admin can restore the key.
- Restoring never brings the old key back: like a rotation, it issues a new
nvk_…(shown once) that you must put on your server. The old key stays invalid; share links stay the same and show up in the new key'sGET shareslist. - After 30 days the key is deleted permanently and the share links it created are closed. The files and folders stay in the account's My Files.
Authentication
Base URL https://generalappapi.nuvaka.com/api/project/v1. Send the key in either header:
Authorization: Bearer nvk_…
X-Api-Key: nvk_…
A wrong, disabled, deleted or rotated key gets 401 invalid_key. Changes take effect immediately.
Scopes
| Scope | Grants |
|---|---|
files.read | list, stat, download |
files.write | upload (single and chunked), create folders, move and rename |
files.delete | delete files or folders (to the trash) |
files.share | create and close public download links |
An operation without its scope returns 403 insufficient_scope; the scope field in the response names the missing scope. A server that only downloads should ask for files.read only.
Paths
- Every
pathis relative to the root folder, with segments separated by/:firmware/v1.2.0/cyd-os.bin. A leading/is ignored;/etc/passwdmeansetc/passwdinside the root. - A
.or..segment, a backslash, control characters, names longer than 255 characters and paths deeper than 32 levels return400 bad_path. - Items can also be addressed by id:
fileIdfor files,folderIdfor folders. An id outside the root returns403 out_of_scope; an unknown id or one from another account returns404 not_found.
Items in responses:
{ "type": "file", "name": "cyd-os.bin", "path": "firmware/v1.2.0/cyd-os.bin", "fileId": "…",
"size": 1048576, "contentType": "application/octet-stream",
"createdAt": "2026-10-17T12:00:00Z", "updatedAt": "2026-10-17T12:00:00Z", "version": 1, "encrypted": false }
{ "type": "folder", "name": "v1.2.0", "path": "firmware/v1.2.0", "folderId": 123, "createdAt": "…" }
Endpoints
| Endpoint | Scope | Description |
|---|---|---|
GET me | — | key name, project, scopes, usedBytes, quotaBytes and limits |
GET list?path=&recursive= | files.read | folder contents; folders first, then files. recursive=true returns the whole subtree (up to 10000 items, truncated: true beyond that) |
GET stat?path= (or fileId, folderId) | files.read | one item: { item } |
GET download?path= (or fileId) | files.read | file content; Range is supported (206) |
POST upload?path=&overwrite= | files.write | single-request upload: raw body, up to 100 MB, Content-Length required. Parent folders are created automatically |
POST uploads | files.write | start a chunked upload for large files (below) |
PUT uploads/{id}/chunks/{index} | files.write | send one chunk |
GET uploads/{id} | files.write | received and missing chunks (resume) |
POST uploads/{id}/complete | files.write | finish the upload |
DELETE uploads/{id} | files.write | abort the upload |
POST mkdir { path } | files.write | create a folder (with intermediate folders; returns the existing one if present) |
POST move { from, to } | files.write | move or rename; to is the new full path. fromFileId / fromFolderId may be used instead of from |
DELETE item?path= (or fileId, folderId) | files.delete | moves to the trash; a folder goes with its contents. The root cannot be deleted |
POST share { path, expiresAt?, maxDownloads? } | files.share | public download link |
GET shares | files.share | active links created by this key |
DELETE shares/{id} | files.share | close a link |
Uploading and overwriting
If a file already exists at the path, the upload returns 409 exists. With overwrite=true a new version of the same file is uploaded: the fileId and any share links stay the same, and links now serve the new content. The Content-Type you send is stored as the file's type. When retrying after a network error, send an X-Idempotency-Key header so the same upload is not stored twice (10 minutes).
{ "success": true, "replaced": false, "item": { "type": "file", "path": "firmware/v1.2.0/cyd-os.bin", "fileId": "…", … } }
Chunked upload (large files)
A single request can carry at most 100 MB; above 64 MB use a chunked upload so a dropped connection does not start over.
- Start
POST uploads{ "path": "images/big.img", "size": 734003200, "contentType": "application/octet-stream", "overwrite": false }→{ uploadId, chunkSize, totalChunks, expiresAt }. Chunks are 32 MB; the session is valid for 24 hours. - Send the chunks
PUT uploads/{id}/chunks/{index}with a raw body; each chunk is exactlychunkSizebytes, the last one whatever remains. An optionalX-Chunk-SHA256header is verified. Re-sending a chunk is safe. On503 buffer_full, wait forRetry-Afterand send the same chunk again; this is not an error. - Finish
POST uploads/{id}/complete→{ success, item }. If you get202 { status: "processing" }, call it again a few seconds later; the same result is returned.
Share link
POST share creates a public link for a file. Anyone with the link can download the file without a key.
{ "success": true, "id": 42, "code": "AbCd1234",
"url": "https://generalappapi.nuvaka.com/share/AbCd1234/download",
"pageUrl": "https://generalappapi.nuvaka.com/share/AbCd1234",
"expiresAt": null, "maxDownloads": null, "path": "firmware/v1.2.0/cyd-os.bin", "fileId": "…" }
urlis the file itself: put it on your page as a direct download link;Rangeis supported.pageUrlis Nuvaka's download page.expiresAt(UTC) andmaxDownloadsare optional. A password can also be set, but a password-protected link first asks for verification on the Nuvaka page; do not use one for direct site links.- Updating the file with
overwrite=truekeeps the link working. Folders and end-to-end encrypted files cannot be shared.
Quota
- A key can have an optional folder quota; exceeding it returns
413 quota_exceededwith"scope": "folder". It counts the current files under the root; older versions and the trash do not count. - Files live in the owner's account; if the account's storage quota is full you get
413 quota_exceededwith"scope": "owner". The account's single-file limit returns413 file_too_large. GET mereports current usage:usedBytes,quotaBytes.
Rate limits
- Per key: 600 reads (GET) and 120 writes (POST, PUT, DELETE; each chunk is one write) per minute.
- When exceeded:
429 rate_limitedwith aRetry-Afterheader and aretryAfterfield. Wait and retry. - At most 5 open chunked uploads per account at a time (
429 too_many_uploads). If the account's daily download quota is used up:429 download_quota_exceeded. - Carry visitor traffic with share links, not with the key: downloads through a link do not count against the key's limits.
Errors
Errors always look like { "success": false, "error": "…", "message": "…" }.
| Code | error | When |
|---|---|---|
| 400 | bad_path | invalid path or no target given |
| 400 | not_a_folder, not_a_file | listing a file, downloading/sharing a folder |
| 400 | root_immutable, bad_target | moving/deleting the root, moving a folder into itself |
| 401 | invalid_key | missing, wrong, disabled, deleted or rotated key |
| 403 | insufficient_scope | the key lacks the required scope |
| 403 | out_of_scope | an item addressed by id is outside the root folder |
| 403 | ip_not_allowed | the request does not come from the key's allowed IPs |
| 404 | not_found, upload_not_found | no such item or upload session |
| 409 | exists | something already exists at the target path |
| 411 | length_required | single upload without Content-Length |
| 413 | quota_exceeded, too_large_for_single, file_too_large | quota or size limit |
| 429 | rate_limited, too_many_uploads, download_quota_exceeded | limits |
| 503 | buffer_full | chunk: wait for Retry-After, send the same chunk again |
curl examples
export NVK=nvk_... # on the server only
B=https://generalappapi.nuvaka.com/api/project/v1
# check the key
curl -sS "$B/me" -H "Authorization: Bearer $NVK"
# upload (parent folders are created; overwrite=true to update the same path)
curl -sS -X POST "$B/upload?path=firmware/v1.2.0/cyd-os.bin&overwrite=true" \
-H "Authorization: Bearer $NVK" -H "Content-Type: application/octet-stream" \
--data-binary @cyd-os.bin
# list
curl -sS "$B/list?path=firmware&recursive=true" -H "Authorization: Bearer $NVK"
# download it back (resume an interrupted download with -C -, which uses Range)
curl -sS -o cyd-os.bin "$B/download?path=firmware/v1.2.0/cyd-os.bin" -H "X-Api-Key: $NVK"
# move / rename
curl -sS -X POST "$B/move" -H "Authorization: Bearer $NVK" -H "Content-Type: application/json" \
-d '{"from":"firmware/v1.2.0/cyd-os.bin","to":"firmware/archive/cyd-os-1.2.0.bin"}'
# delete (to the trash)
curl -sS -X DELETE "$B/item?path=firmware/archive/cyd-os-1.2.0.bin" -H "Authorization: Bearer $NVK"
# public download link
curl -sS -X POST "$B/share" -H "Authorization: Bearer $NVK" -H "Content-Type: application/json" \
-d '{"path":"firmware/v1.2.0/cyd-os.bin"}'
Upload a large file in chunks:
size=$(stat -c%s big.img)
r=$(curl -sS -X POST "$B/uploads" -H "Authorization: Bearer $NVK" -H "Content-Type: application/json" \
-d "{\"path\":\"images/big.img\",\"size\":$size}")
id=$(echo "$r" | jq -r .uploadId); cs=$(echo "$r" | jq -r .chunkSize); n=$(echo "$r" | jq -r .totalChunks)
for i in $(seq 0 $((n-1))); do
dd if=big.img bs=$cs skip=$i count=1 status=none | \
curl -sS -X PUT "$B/uploads/$id/chunks/$i" -H "Authorization: Bearer $NVK" \
-H "Content-Type: application/octet-stream" --data-binary @-
done
curl -sS -X POST "$B/uploads/$id/complete" -H "Authorization: Bearer $NVK"
Typical project site flow
Example: the cyd.nuvaka.com server publishes a new firmware release and gives visitors a download link.
- Upload
When the build finishes, the server uploads the file:
POST upload?path=firmware/v1.3.0/cyd-os.bin. It storesitem.fileIdanditem.pathfrom the response in its own database. - List
When building the releases page, it calls
GET list?path=firmware&recursive=truefor the files, sizes and dates. It caches the result on its side instead of calling the API on every visitor request. - Hand out a link
It calls
POST shareonce per release and stores the returnedurl. The page's "Download" button points straight at that URL; visitors never see the key, and their downloads do not use up the key's rate limit. - Fetch back when needed
When the server itself needs a file (for example to process release notes or a build artifact), it downloads it with
GET download?path=…. - Update or remove
To fix a release in place it uploads with
overwrite=true(the link stays the same and serves the new content). To retire an old release:DELETE shares/{id}andDELETE item?path=….
Nuvaka Apps API v1 · last updated 2026-10-17