API reference
CrabS3 HTTP API
Everything the web interface does is available over HTTP: open a multipart upload session, attach retention rules, share a secret, manage users. JSON in, JSON out.
- Base URL
- {NEXT_PUBLIC_BASE_URL}/api
- Auth
- Session cookie (JWT)
- Health probe
- GET /api/health
Authentication
Sign in once and the session cookie carries every later call. Signup is invite-only: an admin issues a token, the new account redeems it. Accounts with 2FA enabled must post the TOTP code alongside the password.
- POST
/api/auth/loginPassword, plus TOTP code when 2FA is on - POST
/api/auth/signupRedeem an invite token - GET
/api/auth/check-inviteIs this invite token valid — before showing the signup form - POST
/api/auth/logoutClear the session - GET
/api/auth/meCurrent user and admin flag - DELETE
/api/auth/meDelete your own account and everything it owns — sessions, invites sent, secrets, folders - POST
/api/auth/inviteIssue an invite (admin)
Multipart upload
A hash check comes first — if you already have this exact file, the server links a new entry to it and no bytes move. Otherwise: open a session, upload parts in parallel, complete — then call finish once for the whole batch, not per file. A dropped connection reattaches to the same session instead of restarting the transfer, skipping whatever parts already landed.
- POST
/api/upload/dedupe-checkGiven a content hash, links a new file to an already-uploaded one instead of re-uploading it.
- POST
/api/upload/multipart/startOpens a session for one file, returns an upload id, a token and the part size to use.
- POST
/api/upload/multipart/partUploads one part. Parts run in parallel and retry independently.
- POST
/api/upload/multipart/resumeReattaches to an interrupted session; returns which parts already landed.
- POST
/api/upload/multipart/set-hashAttaches a content hash to a session started before hashing finished.
- POST
/api/upload/multipart/completeSeals the object and attaches retention rules, password and folder name.
- POST
/api/upload/multipart/finishCall once after every file in the batch is complete — sends the upload notification email and fires webhooks. Not per-file.
- POST
/api/upload/multipart/abortCancels the session and discards uploaded parts.
Request
POST /api/upload/multipart/start X-Filename: archive.zip X-Folder-Id: 9f3c2b1a-6e4d-4f0a-8c2e-1a2b3c4d5e6f X-File-Size: 8589934592 X-Content-Hash: 6b7a9c3e2f1d8a4b… Content-Type: application/zip
Response 200
{
"fileId": "b7e1f2a3-4c5d-4e6f-8a9b-0c1d2e3f4a5b",
"uploadId": "2~aBcDeFgHiJkLmNoPqRsT…",
"token": "eyJhbGciOiJIUzI1NiIs…",
"chunkSize": 104857600
}Files & downloads
Download is two steps on purpose: a POST validates the password and the remaining quota, then a GET streams the bytes. Requesting several files returns a zip built on the fly.
- GET
/api/checkfileIs this share link still valid — password required, expired, quota left? - POST
/api/download/:idValidate password and quota, return metadata - GET
/api/download/:id/streamStream the bytes, or a zip for several files - DELETE
/api/deleteRemove a file you own. Returns 409 with a mode choice if others share its content.
Expired links answer 404, not 200. A consumed or timed-out share resolves server-side before the page renders, so monitoring and search engines both see a real status code.
Secrets
The check call tells you whether a secret exists and whether a password is required — without leaking the payload or confirming a correct password. Reading consumes the quota.
- POST
/api/secret/uploadStore a secret, get a share link - POST
/api/secret/checkDoes it exist, and is a password needed? - POST
/api/secret/getRead it — consumes the quota
Services
A service is a scoped API key for a third-party app or script: its own folder, quota and status, independent of any user account. Two ways to get its token — an admin creates the service directly and gets the bearer token back immediately, or an admin issues a redeemable invite code that a third party trades for their own token via join, without ever needing a CrabS3 account. Either way, the result is a bearer token — send it as Authorization: Bearer <token> — separate from the session cookie used everywhere else on this page.
- POST
/api/services/createPath 1 — create a service, get its bearer token back immediately (admin) - POST
/api/services/create/invitePath 2 — issue a redeemable invite code for an existing service (admin) - POST
/api/services/joinPath 2 — redeem an invite code for a bearer token, no account needed - GET
/api/services/:uuidPublic info about a service — name, image, status - GET
/api/services/listList every service with usage counts (admin) - PUT
/api/services/updateChange a service's status or image (admin) - DELETE
/api/services/delete/:idDelete a service and its folder (admin) - POST
/api/services/uploadBearer token. Single presigned PUT URL — no multipart, no dedup. - GET
/api/services/downloadBearer token. A share link, or a presigned URL per file.
Request
POST /api/services/upload Authorization: Bearer eyJhbGciOiJIUzI1NiIs… X-Filename: report.pdf X-File-Size: 2481203 Content-Type: application/pdf
Response 200
{
"url": "https://…?X-Amz-Signature=…",
"fileId": "4d5e6f7a-8b9c-…",
"folderId": "9f3c2b1a-…"
}Not the multipart flow. Service uploads are a single presigned PUT to your bucket — no session split across parts, no resume, no dedup check. Fine for the kind of files scripts and integrations usually push; for anything large or interactive, the multipart flow above is the one to use.
Personal access tokens
Acts as you, without the session cookie — for scripts, cron jobs, CI. Each token carries exactly one scope and an expiry you choose (7, 30, 90, 180 or 365 days). Create and revoke them from /me; the value is shown once, at creation.
- GET
/api/accessTokenList your tokens — name, scope, expiry. Never the token value again - POST
/api/accessTokenCreate one: name, scopes: ["READ"|"WRITE"|"DELETE"|"ADMIN"], expires_at (days) - DELETE
/api/accessToken?id=:idRevoke a token
| Scope | Allows |
|---|---|
READ | GET requests only |
WRITE | Any method except DELETE |
DELETE | DELETE requests only |
ADMIN | Everything, including /api/admin/* |
scopes is an array. The API accepts more than one scope on the same token — the /me page currently only offers picking one at creation time.
Two-factor authentication
TOTP, self-service. Create returns a secret and an otpauth:// URI to render as a QR code; from then on, login must include the current code alongside the password.
- POST
/api/2fa/createGenerate a secret and QR URI for the current account, enables 2FA immediately - GET
/api/2fa/disableDisable 2FA for the current account
Account
Per-user file and folder listing, profile updates, the webhook settings that drive upload and download notifications, download history, and reading specific instance settings by key.
- GET
/api/dashboard/filesFiles owned by the current user - GET
/api/dashboard/foldersFolders you own or have files in, with a file count each - PATCH
/api/dashboard/folders/:idRename a folder - DELETE
/api/dashboard/folders/:idDelete a folder and every file in it - POST
/api/dashboard/coldtohotRestore a folder's files from cold storage back to hot - PATCH
/api/dashboard/meUpdate profile and preferences - GET
/api/communicationRead notification and webhook settings - POST
/api/communicationUpdate notification and webhook settings - GET
/api/fingerprint/:id?type=file|folderDownload history for a file or folder — IP, user agent, timestamp - GET
/api/settings?keys=a,bRead specific instance settings by key (comma-separated)
Admin
Requires an account flagged as admin. Storage totals, user management, quotas, and the audit log with its minimum level.
- GET
/api/admin/statsStorage used, file count, user count - GET
/api/admin/usersList every account - GET
/api/admin/users/:idOne account with its files - DELETE
/api/admin/users/:idDelete an account and its objects - PUT
/api/admin/users/:id/edit-quotaChange a storage quota - POST
/api/admin/users/:id/reset-passwordForce a password reset - GET
/api/admin/logsAudit log, filterable by level, action and date - PATCH
/api/admin/logsSet the minimum recorded log level - GET
/api/admin/settingsList every instance setting and its current value - PATCH
/api/admin/settingsUpdate one setting: { key, value } - DELETE
/api/admin/settings?key=:keyReset one setting to its default - POST
/api/admin/settingsSync the settings catalog — creates any missing rows with their defaults
Errors
Every failure returns { "error": "…" } with a meaningful status. The ones worth handling explicitly:
| Code | Meaning | When |
|---|---|---|
400 | Bad request | A required field or query parameter is missing |
401 | Unauthenticated | No valid session, or the wrong share password |
403 | Forbidden | Authenticated, but not allowed — admin routes, other people’s files |
404 | Not found | Unknown id, or a share link that expired |
409 | Conflict | Deleting a file that shares content with others — retry with an explicit mode |
410 | Gone | The download quota ran out while the page was open |
413 | Payload too large | A single request exceeded the configured body limit |
Request collections for every endpoint live in doc/api.