Skip to content
Skip to content
Get started
/Developers

Developers

API reference

zcrypt exposes a JSON REST API. Encryption happens on the client, so the API only ever moves opaque ciphertext, metadata, and chunks, never plaintext or keys.

Basics

  • •Base URL. Your backend origin, with every route under /api. On a self-hosted instance this is whatever you set as BACKEND_URL.
  • •Content type. Request and response bodies are JSON, except chunk transfers, which send and receive raw bytes. JSON request bodies are capped at 1 MB.
  • •Errors. Non-2xx responses return { "error": "message" } with a matching HTTP status.
  • •Rate limits. Requests are rate-limited per IP. Auth and public (share / send / pad) routes have their own tighter limits; the SSE and WebSocket endpoints are exempt.

Stability

This documents the routes the web, desktop, and Android apps use today. zcrypt is open source and pre-1.0. Treat the surface as evolving, and read RegisterRoutes in app/backend/cmd/server.go as the authoritative route table.

Authentication

Authenticate by sending a JSON Web Token in the Authorization header. Logging in returns an access token (and a refresh token); send the access token on every protected request.

Authorization header
Authorization: Bearer <access-token>

When an account has two-factor enabled, login returns a 2FA challenge instead of tokens; complete it with POST /api/auth/2fa/verify. Access tokens are short-lived. Use POST /api/auth/refresh to mint a new one.

MethodPathPurpose
POST/api/auth/registerCreate an account.
POST/api/auth/loginSign in; returns tokens or a 2FA challenge.
POST/api/auth/refreshExchange a refresh token for a new access token.
POST/api/auth/logoutInvalidate the current session.
POST/api/auth/2fa/verifyComplete a TOTP challenge during login.
POST/api/auth/2fa/setupGenerate a TOTP secret (authenticated).
POST/api/auth/2fa/enableEnable 2FA after verifying a code.
POST/api/auth/forgot-passwordSend a password-reset email.
POST/api/auth/reset-passwordReset a password with a token.
POST/api/auth/verify-emailVerify an email address.
POST/api/auth/magic-linkRequest a passwordless sign-in link.
GET/api/auth/meReturn the current user.
GET/api/auth/oauth/{provider}Begin a Google/GitHub OAuth flow.

Files & folders

Files and folders carry only encrypted metadata. Folders can be nested, renamed, moved, and given their own password; deletes are soft (Trash) until purged. File names and folder names are ciphertext on the server.

MethodPathPurpose
GET/api/filesList files (filterable by folder).
GET/api/files/trashList soft-deleted files.
DELETE/api/files/{id}Move a file to Trash.
POST/api/files/bulk-deleteMove many files to Trash.
PATCH/api/files/{id}/moveMove a file into a folder.
POST/api/files/{id}/restoreRestore from Trash.
DELETE/api/files/{id}/purgePermanently delete a file.
GET/api/foldersList folders.
POST/api/foldersCreate a folder.
PATCH/api/folders/{id}Rename a folder.
PATCH/api/folders/{id}/moveMove a folder.
POST/api/folders/{id}/passwordSet a folder password.
DELETE/api/folders/{id}Delete a folder.

Upload (chunked)

Uploads are client-side encrypted and chunked. Initialise a session, push each encrypted chunk (or upload it directly to the platform via a presigned URL where supported), then complete the session. Sessions are resumable: query status and re-send only the chunks that are missing.

MethodPathPurpose
POST/api/upload/initStart an upload session; returns a session id. The body must carry encrypted_name, or the server answers 426 with code client_update_required. Files over 10 GiB get 413.
PUT/api/upload/{sid}/chunk/{idx}Upload one encrypted chunk through the server.
POST/api/upload/{sid}/presign/{idx}Get a presigned URL to upload a chunk directly.
POST/api/upload/{sid}/confirm/{idx}Confirm a directly-uploaded chunk.
POST/api/upload/{sid}/completeFinalise the file once all chunks are in.
GET/api/upload/{sid}/statusReport which chunks are received (resume).
DELETE/api/upload/{sid}Cancel an in-flight upload.

Chunk integrity headers

Chunk uploads carry an X-Chunk-SHA256 hash and an X-Chunk-Compressed flag so the server can verify each chunk and record how it was packed, without ever decrypting it.

Download (chunked)

Downloads fetch metadata, then pull chunks by index. The client verifies each chunk, reassembles, decrypts with your passphrase-derived key, and decompresses, all locally.

MethodPathPurpose
GET/api/files/{id}/metaFile metadata: size, chunk count, salt/IV.
POST/api/files/metaBatch metadata for up to 200 file ids in one request.
GET/api/files/{id}/chunks/{idx}Fetch one encrypted chunk by index.

Sharing

Share links expose a single file to anyone with the token, optionally guarded by a password and bounded by an expiry or download limit. The public read routes are unauthenticated and rate-limited; a share password is supplied via the X-Share-Password header.

MethodPathPurpose
POST/api/sharesCreate a share link (authenticated).
GET/api/sharesList your share links.
DELETE/api/shares/{id}Revoke a share link.
GET/api/share/{token}Public: share info (name, size, flags).
GET/api/share/{token}/metaPublic: file metadata for the share.
GET/api/share/{token}/chunks/{idx}Public: fetch a shared chunk.

Folder links

A folder link publishes a set of files behind a single public token. The link key is generated on the client and lives only in the URL fragment, it is never sent to the server, and each file’s content key is re-wrapped under it, so the public routes only ever return opaque ciphertext. A link can carry a password (sent in the X-Share-Password header), an expiry, and a download cap. The public read routes are unauthenticated and rate-limited.

MethodPathPurpose
POST/api/folder-sharesCreate a folder link (authenticated).
GET/api/folder-sharesList your folder links.
DELETE/api/folder-shares/{id}Revoke a folder link.
GET/api/folder-share/{token}Public: link info and the file listing.
GET/api/folder-share/{token}/files/{fid}/metaPublic: one file's metadata + wrapped key.
GET/api/folder-share/{token}/files/{fid}/chunks/{idx}Public: fetch a chunk of a linked file.

Shared vaults (spaces)

A shared vault (a “space”) lets several accounts share files under role-based access. The space has its own random key that the client seals to each member’s public key (see User keys below), and every shared file’s content key is re-wrapped under the space key. The server only ever stores these opaque grants, it can add, remove, and rotate them but never open them. Owners and admins manage members; only the owner can rotate the key or delete the space. Adding or removing files is limited to editors and admins.

MethodPathPurpose
GET/api/shared-vaultsList spaces you belong to.
POST/api/shared-vaultsCreate a space.
GET/api/shared-vaults/{id}Space detail with members and files.
DELETE/api/shared-vaults/{id}Delete a space (owner).
PATCH/api/shared-vaults/{id}Edit the space name, description, or size limit (owner or admin).
POST/api/shared-vaults/{id}/membersAdd a member with a sealed grant (owner or admin).
PATCH/api/shared-vaults/{id}/members/{uid}Change a member's role (owner or admin; only the owner can grant or change admin).
DELETE/api/shared-vaults/{id}/members/{uid}Remove a member (owner or admin; only the owner can remove an admin). Members can remove themselves.
POST/api/shared-vaults/{id}/filesShare a file into the space (editor/admin).
POST/api/shared-vaults/{id}/files/batchShare many files into the space in one transaction (editor/admin). The free plan caps a batch at max_space_batch_files (default 200).
DELETE/api/shared-vaults/{id}/files/{fid}Unshare a file (editor/admin).
POST/api/shared-vaults/{id}/rotateRotate the space key after a change (owner).

Rotation is what makes removal a revocation

Rotating re-seals a fresh space key to the remaining members and re-wraps every file’s key under it, so a removed member’s old grant, and any copy of the old key, can no longer open the space’s files.

User keys

To seal a space key to another account, every user gets an X25519 keypair generated on the client. The private key is wrapped under your passphrase-derived key and stored only as ciphertext the server can’t read; the public key and a short fingerprint are stored in the clear so others can seal grants to you. These routes never return anyone else’s private key.

MethodPathPurpose
GET/api/keys/meYour key record (wrapped private key, wrapped master key if set, and the master_key_writes flag), or null.
POST/api/keysPublish or rotate your keypair.
PUT/api/keys/masterStore your wrapped account master key (mk1). Set once: a second write returns 409.
GET/api/keys/lookupResolve a user's public key by email or username (identifier query).
GET/api/keys/user/{id}Fetch another user's public key by id.
GET/api/users/{id}/avatarA user's avatar image. Visible to that user and to people who share a space with them; anyone else gets 404.

Send & Pad

Send lets anyone upload an encrypted file without an account and hand over a token; Pad stores a one-time encrypted note. Both are unauthenticated and rate-limited by IP.

MethodPathPurpose
POST/api/send/initStart an anonymous send.
PUT/api/send/{sid}/chunk/{idx}Upload an encrypted chunk.
POST/api/send/{sid}/completeFinalise the send; returns a token.
GET/api/send/{token}Fetch send info / metadata / chunks.
POST/api/padCreate an encrypted one-time pad.
GET/api/pad/{token}Read pad info, then its content.

Timed vaults

A timed vault groups files under a name and a countdown. The expiry is a label, not a delete trigger, deleting a vault never deletes the files it references. The expiry must be at least an hour out.

MethodPathPurpose
GET/api/vaultsList your timed vaults.
POST/api/vaultsCreate one (name, expires_at, file ids).
GET/api/vaults/{id}Get a timed vault.
DELETE/api/vaults/{id}Delete the vault (not its files).

Snapshots & integrity

Snapshots capture a point-in-time manifest of your file list; the integrity monitor records each file’s SHA-256 and size so a later check can flag a hash that changed. Both are metadata-only, no file contents are stored or restored. See Snapshots & integrity.

MethodPathPurpose
GET/api/snapshotsList vault snapshots.
POST/api/snapshotsCapture a new snapshot.
GET/api/snapshots/{id}Get one snapshot.
DELETE/api/snapshots/{id}Delete a snapshot.
GET/api/integrityList integrity references.
POST/api/integrityRecord a file's hash + size reference.
POST/api/integrity/checkCheck a file against its latest reference.
GET/api/integrity/changesList files whose hash no longer matches.

Offline & clipboard

Offline pins mark files to keep available on a device; the encrypted clipboard syncs small snippets between your own devices. Clipboard items are encrypted on the client (the server stores only ciphertext, capped at 512 KB per item) and a push notifies your other devices over the event stream.

MethodPathPurpose
GET/api/offlineList offline pins (optional device_id).
POST/api/offlinePin a file for offline access.
DELETE/api/offline/{fileId}Unpin a file.
POST/api/clipboardPush an encrypted clipboard item (text / image / link).
GET/api/clipboardList recent clipboard items.
GET/api/clipboard/{id}Fetch an item's encrypted bytes.
DELETE/api/clipboard/{id}Delete a clipboard item.

Sync folders

Sync folders are a per-device registry of local folders the desktop client keeps in sync. The server stores only the configuration and the stats a client reports back, the actual file syncing happens on your machine.

MethodPathPurpose
GET/api/sync/foldersList sync-folder configs.
POST/api/sync/foldersRegister a folder for sync.
PUT/api/sync/folders/{id}Update a folder (enable/label).
PUT/api/sync/folders/{id}/statsReport sync results (file count, total size).
DELETE/api/sync/folders/{id}Remove a folder config.

Events (SSE)

Real-time progress (uploads, syncs) streams over Server-Sent Events. Because EventSource cannot set headers, first trade your access token for a single-use ticket, then open the stream with it. The ticket is valid for 30 seconds, so the token itself never appears in a URL or a proxy log.

text/event-stream
POST /api/sse/ticket            -> {"ticket": "..."}
GET  /api/events?ticket=<ticket>

The stream carries upload and sync progress plus a clipboard event, emitted to your other connections when a new clipboard item is pushed. It closes when the access token it was opened with expires, or as soon as your sessions are revoked, so reconnect with a fresh ticket. Each account can hold a limited number of open streams. A separate WebSocket endpoint, GET /api/transfer/ws, backs live device-to-device transfer. Both long-lived endpoints bypass the per-request rate limiter.

Quota & config

These routes report account limits, connected storage, and instance settings. Most are read-only for normal users; writing config requires admin.

MethodPathPurpose
GET/api/quotaStorage quota and max concurrent uploads.
GET/api/configEffective instance configuration.
GET/api/platforms/statusConnection status per storage platform.
POST/api/platforms/connectConnect a platform token.
DELETE/api/platforms/disconnectDisconnect a platform.
GET/api/reposList repositories in your storage pool.
GET/api/plansPublic plan configs (no auth).
GET/api/healthHealth check (no auth).
GET/api/geo/countryThe visitor's two-letter country code taken from edge headers, or an empty string (no auth).

Admin

Admin routes require an account with the admin role. They cover user management, quotas and plans, global tokens, and audit data.

MethodPathPurpose
GET/api/admin/usersList all users.
GET/api/admin/statsSystem statistics.
PUT/api/admin/users/{id}/roleSet a user's role. Send the acting admin's password, and a 2FA code if enabled.
PUT/api/admin/users/{id}/planSet a user's plan.
PUT/api/admin/users/{id}/quotaSet a user's quota.
DELETE/api/admin/users/{id}Delete a user. Needs the same re-authentication as a role change.
GET/api/admin/tokensList global storage tokens.
GET/api/admin/auditRead the audit log.
PUT/api/admin/settings/master-key-writesTurn master-key (mk1) writes on or off. Off by default; GET on the same path reads the current value.

Looking to run your own instance? See Self-hosting and Architecture.