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-check

    Given a content hash, links a new file to an already-uploaded one instead of re-uploading it.

  • POST/api/upload/multipart/start

    Opens a session for one file, returns an upload id, a token and the part size to use.

  • POST/api/upload/multipart/part

    Uploads one part. Parts run in parallel and retry independently.

  • POST/api/upload/multipart/resume

    Reattaches to an interrupted session; returns which parts already landed.

  • POST/api/upload/multipart/set-hash

    Attaches a content hash to a session started before hashing finished.

  • POST/api/upload/multipart/complete

    Seals the object and attaches retention rules, password and folder name.

  • POST/api/upload/multipart/finish

    Call once after every file in the batch is complete — sends the upload notification email and fires webhooks. Not per-file.

  • POST/api/upload/multipart/abort

    Cancels 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
What each access token scope allows
ScopeAllows
READGET requests only
WRITEAny method except DELETE
DELETEDELETE requests only
ADMINEverything, 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:

HTTP status codes returned by the CrabS3 API
CodeMeaningWhen
400Bad requestA required field or query parameter is missing
401UnauthenticatedNo valid session, or the wrong share password
403ForbiddenAuthenticated, but not allowed — admin routes, other people’s files
404Not foundUnknown id, or a share link that expired
409ConflictDeleting a file that shares content with others — retry with an explicit mode
410GoneThe download quota ran out while the page was open
413Payload too largeA single request exceeded the configured body limit

Request collections for every endpoint live in doc/api.