File API
Upload, list, retrieve and delete files, organize folders and follow imports from scripts and CI. Bytes always go directly to and from storage, never through this API.
Authentication
Create a key in API keys (workspace owners and admins). Send it on every request as Authorization: Bearer sharer_sk_…. The key is shown once; Sharer stores only a hash of it.
- A key belongs to one workspace and acts for the person who holds it (its custodian), within that person's current access.
- Every key expires (30 days, 90 days, 180 days, 1 year). Rotation issues a new key and can keep the old one working for 24 hours. Revoking takes effect on the next request.
- Keys never change passwords, the team, branding, settings or other keys, and never bypass download passwords. Requests with a key ignore cookies and need no same-origin headers. Keep keys on servers; don't ship them to browsers.
- A workspace can have at most 25 active keys.
export SHARER_URL="https://<your Sharer origin>"export SHARER_API_KEY="sharer_sk_..." # from /api-keys, shown oncecurl -sS "$SHARER_URL/api/v1/files?limit=50" \ -H "Authorization: Bearer $SHARER_API_KEY"Scopes
Give each key only the scopes it needs. A request outside the key's scopes answers 403 insufficient_scope.
| Scope | Allows |
|---|---|
files:read | List, search and read file and folder details.
|
files:write | Create folders, rename, move and delete files and folders.
|
uploads:write | Start, resume, complete and cancel uploads. Can't read or delete files.
|
downloads:read | Start file and folder ZIP downloads. Passwords still apply.
|
imports:read | List imports, their items and reports.
|
imports:write | Create, confirm, pause, resume and cancel imports.
|
uploads:write can start, resume, complete and cancel uploads, but can't list, read, download or delete files.Requests and errors
- Bodies are JSON (
Content-Type: application/json, UTF-8). Every response is JSON withCache-Control: no-store. - Idempotency: creates (
POST /folders,POST /uploads,POST /migrations) and folder deletes need anIdempotency-Keyheader (8 to 128 letters, digits,-or_). Retrying with the same key returns the first result; reusing it for a different request answersidempotency_conflict. - Versions: files and folders carry an
ETag.PATCHandDELETEneed it inIf-Match(428 without it). If the item changed since, you get 412revision_conflict: read it again and retry. - Pagination: listings return up to 100 items (default 50) and an opaque
nextCursor; pass it back ascursorwith the same folder, sort and query. Import items page withafterandnextAfter.
Every error uses one envelope. Quote the request ID when you contact support.
{ "error": { "code": "insufficient_scope", "message": "This API key doesn't have the scope this request needs.", "retryable": false, "requestId": "6f1c2c1e-0d55-4c1b-9a51-2d0f3b8f5e21" }}| Status | Code | Meaning |
|---|---|---|
| 401 | invalid_api_key | This API key isn't valid. Check the Authorization header or create a new key. |
| 403 | api_key_suspended | This API key is suspended because the person it acts for can no longer manage keys here. An owner can reassign it. |
| 403 | insufficient_scope | This API key doesn't have the scope this request needs. |
| 403 | forbidden | You don't have permission to do that. |
| 404 | not_found | We couldn't find that. |
| 400 | invalid_input | Check the highlighted fields and try again. |
| 400 | invalid_idempotency_key | Reload the page and try again. |
| 409 | idempotency_conflict | This request conflicts with an earlier one. Reload and try again. |
| 412 | revision_conflict | This changed since you opened it. Reload and try again. |
| 409 | name_taken | A folder with that name already exists here. |
| 409 | quota_exceeded | This workspace has reached its storage limit. |
| 409 | upload_incomplete | Some of the file hasn't arrived yet. Resume the upload and try again. |
| 410 | upload_expired | This upload expired. Start it again. |
| 429 | rate_limited | Too many attempts. Try again later. |
| 503 | api_not_ready | We're finishing an update. Try again in a minute. |
| 503 | unavailable | Something went wrong on our side. Try again. |
Key failures also carry WWW-Authenticate: Bearer with error="invalid_token" or error="insufficient_scope". Errors with retryable: true (and 503s with Retry-After) are safe to retry after a pause.
Files and folders
List a folder with GET /api/v1/files?folderId=… (the top folder when omitted), or search with ?search=…&scope=…. Folders list with GET /api/v1/folders?parentId=…. File and folder IDs and public links never change on rename or move; new uploads always get new links.
curl -sS -X POST "$SHARER_URL/api/v1/folders" \ -H "Authorization: Bearer $SHARER_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"parentId": null, "name": "Releases"}'# ETag from GET /api/v1/files/{id}, e.g. "r3-p7"curl -sS -X PATCH "$SHARER_URL/api/v1/files/$FILE_ID" \ -H "Authorization: Bearer $SHARER_API_KEY" \ -H "Content-Type: application/json" \ -H 'If-Match: "r3-p7"' \ -d '{"name": "release-1.2.zip"}'
curl -sS -X DELETE "$SHARER_URL/api/v1/files/$FILE_ID" \ -H "Authorization: Bearer $SHARER_API_KEY" \ -H 'If-Match: "r4-p7"'Deleting a large folder continues in the background: the answer is 202 with a job to poll. Password changes and withdrawals are only available in the app.
Uploads
Files up to 105 MB upload with one signed PUT; larger ones in parts of 33.6 MB or more, up to 53.7 GB (sizes in MiB/GiB). Each signed URL covers one exact part and its Content-MD5, and lives at most 60 minutes. Sessions stay resumable for 24 hours after the last activity.
FILE=release.zipMD5=$(openssl dgst -md5 -binary "$FILE" | base64)SIZE=$(wc -c < "$FILE" | tr -d ' ')
# 1. Reserve the upload. Small files get a signed PUT right away.curl -sS -X POST "$SHARER_URL/api/v1/uploads" \ -H "Authorization: Bearer $SHARER_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d "{\"folderId\": \"$FOLDER_ID\", \"name\": \"$FILE\", \"sizeBytes\": $SIZE, \"contentMd5\": \"$MD5\"}" > upload.json
# 2. Send the bytes straight to storage with the signed URL and headers.HEADERS=()while IFS= read -r header; do HEADERS+=(-H "$header"); done \ < <(jq -r '.put.headers | to_entries[] | "\(.key): \(.value)"' upload.json)curl -sS -X PUT "$(jq -r .put.url upload.json)" "${HEADERS[@]}" --data-binary @"$FILE"
# 3. Commit. Retries return the same file ID and link.curl -sS -X POST "$SHARER_URL/api/v1/uploads/$(jq -r .session.id upload.json)/complete" \ -H "Authorization: Bearer $SHARER_API_KEY" \ -H "Content-Type: application/json" \ -d '{}'# Multipart (session.mode = "multipart"): sign up to 8 parts per call.curl -sS -X POST "$SHARER_URL/api/v1/uploads/$UPLOAD_ID/parts" \ -H "Authorization: Bearer $SHARER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"parts": [{"partNumber": 1, "contentMd5": "<base64 MD5 of part 1>"}]}'
# Resume: the session lists the parts storage already has.curl -sS "$SHARER_URL/api/v1/uploads/$UPLOAD_ID" \ -H "Authorization: Bearer $SHARER_API_KEY"upload_incomplete on complete means some parts are missing: sign and send them, then complete again. After completion the file is verified (size and SHA-256) before its link goes live; poll GET /api/v1/uploads/{id} for its state. Keys upload files only (purpose: "file").
Retrieving bytes
Start a download with a fresh intentId (a UUID; repeat it to retry the same start). A ready answer carries a short-lived URL on the download origin: fetch it with any HTTP client, resume with Range, and verify the published SHA-256. Treat that URL as a secret.
curl -sS -X POST "$SHARER_URL/api/v1/files/$FILE_ID/downloads" \ -H "Authorization: Bearer $SHARER_API_KEY" \ -H "Content-Type: application/json" \ -d "{\"intentId\": \"$(uuidgen | tr A-Z a-z)\"}" > grant.json
# state "ready": fetch the bytes from the download origin. Resumable.curl -sS -C - -o release.zip "$(jq -r .url grant.json)"- Folders download as a ZIP from
POST /api/v1/folders/{id}/downloads. Large folders answerpreparingfirst: repeat the request with the sameintentIduntil it'sready. - Password-protected items answer
locked. Keys never bypass passwords; unlock them in a browser instead. - Other states:
not_found,retired,paused,expired(start again) andfailedwith a reason.
Imports
Follow and control imports you started in Import, or create them with POST /api/v1/migrations. Poll GET /api/v1/migrations/{id} for progress, page through /items, and download the CSV /report to map old links to new ones. Pause, resume and cancel are available with imports:write; disconnecting a source and the local helper token stay in the app.
curl -sS "$SHARER_URL/api/v1/migrations/$IMPORT_ID" \ -H "Authorization: Bearer $SHARER_API_KEY"
curl -sS -X POST "$SHARER_URL/api/v1/migrations/$IMPORT_ID/pause" \ -H "Authorization: Bearer $SHARER_API_KEY"Rate limits
- Per key, per 10 minutes: 1,200 reads and 300 writes; upload calls 600; import commands 120.
- Invalid keys are limited per client IP (20 per 10 minutes). Over that, every request from that IP with a
Bearertoken answers 429 until the window resets. - Over a limit you get 429
rate_limitedwithRetry-Afterin seconds. Storage quota still applies:quota_exceededis a 409. - Multipart parts are at least 5.2 MB each; a 53.7 GB file needs about 200 signing calls.
These limits are provisional and may change before general availability.
