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's GET shares list.
  • 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

ScopeGrants
files.readlist, stat, download
files.writeupload (single and chunked), create folders, move and rename
files.deletedelete files or folders (to the trash)
files.sharecreate 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 path is relative to the root folder, with segments separated by /: firmware/v1.2.0/cyd-os.bin. A leading / is ignored; /etc/passwd means etc/passwd inside the root.
  • A . or .. segment, a backslash, control characters, names longer than 255 characters and paths deeper than 32 levels return 400 bad_path.
  • Items can also be addressed by id: fileId for files, folderId for folders. An id outside the root returns 403 out_of_scope; an unknown id or one from another account returns 404 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

EndpointScopeDescription
GET me—key name, project, scopes, usedBytes, quotaBytes and limits
GET list?path=&recursive=files.readfolder 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.readone item: { item }
GET download?path= (or fileId)files.readfile content; Range is supported (206)
POST upload?path=&overwrite=files.writesingle-request upload: raw body, up to 100 MB, Content-Length required. Parent folders are created automatically
POST uploadsfiles.writestart a chunked upload for large files (below)
PUT uploads/{id}/chunks/{index}files.writesend one chunk
GET uploads/{id}files.writereceived and missing chunks (resume)
POST uploads/{id}/completefiles.writefinish the upload
DELETE uploads/{id}files.writeabort the upload
POST mkdir { path }files.writecreate a folder (with intermediate folders; returns the existing one if present)
POST move { from, to }files.writemove or rename; to is the new full path. fromFileId / fromFolderId may be used instead of from
DELETE item?path= (or fileId, folderId)files.deletemoves to the trash; a folder goes with its contents. The root cannot be deleted
POST share { path, expiresAt?, maxDownloads? }files.sharepublic download link
GET sharesfiles.shareactive links created by this key
DELETE shares/{id}files.shareclose 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.

  1. 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.

  2. Send the chunks

    PUT uploads/{id}/chunks/{index} with a raw body; each chunk is exactly chunkSize bytes, the last one whatever remains. An optional X-Chunk-SHA256 header is verified. Re-sending a chunk is safe. On 503 buffer_full, wait for Retry-After and send the same chunk again; this is not an error.

  3. Finish

    POST uploads/{id}/complete → { success, item }. If you get 202 { status: "processing" }, call it again a few seconds later; the same result is returned.

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": "…" }
  • url is the file itself: put it on your page as a direct download link; Range is supported. pageUrl is Nuvaka's download page.
  • expiresAt (UTC) and maxDownloads are 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=true keeps 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_exceeded with "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_exceeded with "scope": "owner". The account's single-file limit returns 413 file_too_large.
  • GET me reports 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_limited with a Retry-After header and a retryAfter field. 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": "…" }.

CodeerrorWhen
400bad_pathinvalid path or no target given
400not_a_folder, not_a_filelisting a file, downloading/sharing a folder
400root_immutable, bad_targetmoving/deleting the root, moving a folder into itself
401invalid_keymissing, wrong, disabled, deleted or rotated key
403insufficient_scopethe key lacks the required scope
403out_of_scopean item addressed by id is outside the root folder
403ip_not_allowedthe request does not come from the key's allowed IPs
404not_found, upload_not_foundno such item or upload session
409existssomething already exists at the target path
411length_requiredsingle upload without Content-Length
413quota_exceeded, too_large_for_single, file_too_largequota or size limit
429rate_limited, too_many_uploads, download_quota_exceededlimits
503buffer_fullchunk: 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.

  1. Upload

    When the build finishes, the server uploads the file: POST upload?path=firmware/v1.3.0/cyd-os.bin. It stores item.fileId and item.path from the response in its own database.

  2. List

    When building the releases page, it calls GET list?path=firmware&recursive=true for the files, sizes and dates. It caches the result on its side instead of calling the API on every visitor request.

  3. Hand out a link

    It calls POST share once per release and stores the returned url. 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.

  4. 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=….

  5. 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} and DELETE item?path=….

Nuvaka Apps API v1 · last updated 2026-10-17