Skip to content

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.
Setup
export SHARER_URL="https://<your Sharer origin>"export SHARER_API_KEY="sharer_sk_..."   # from /api-keys, shown once
List the top folder
curl -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.

ScopeAllows
files:readList, search and read file and folder details.
  • GET /api/v1/files
  • GET /api/v1/files/{id}
  • GET /api/v1/folders
  • GET /api/v1/folders/{id}
files:writeCreate folders, rename, move and delete files and folders.
  • POST /api/v1/folders
  • PATCH /api/v1/files/{id}
  • PATCH /api/v1/folders/{id}
  • DELETE /api/v1/files/{id}
  • DELETE /api/v1/folders/{id}
uploads:writeStart, resume, complete and cancel uploads. Can't read or delete files.
  • GET|POST /api/v1/uploads
  • GET|DELETE /api/v1/uploads/{id}
  • POST /api/v1/uploads/{id}/parts
  • POST /api/v1/uploads/{id}/complete
downloads:readStart file and folder ZIP downloads. Passwords still apply.
  • POST /api/v1/files/{id}/downloads
  • POST /api/v1/folders/{id}/downloads
imports:readList imports, their items and reports.
  • GET /api/v1/migrations
  • GET /api/v1/migrations/{id}
  • GET /api/v1/migrations/{id}/items
  • GET /api/v1/migrations/{id}/report
imports:writeCreate, confirm, pause, resume and cancel imports.
  • POST /api/v1/migrations
  • POST /api/v1/migrations/{id}/decisions
  • POST /api/v1/migrations/{id}/start
  • POST /api/v1/migrations/{id}/{pause,resume,cancel}

Requests and errors

  • Bodies are JSON (Content-Type: application/json, UTF-8). Every response is JSON with Cache-Control: no-store.
  • Idempotency: creates (POST /folders, POST /uploads, POST /migrations) and folder deletes need an Idempotency-Key header (8 to 128 letters, digits, - or _). Retrying with the same key returns the first result; reusing it for a different request answers idempotency_conflict.
  • Versions: files and folders carry an ETag. PATCH and DELETE need it in If-Match (428 without it). If the item changed since, you get 412 revision_conflict: read it again and retry.
  • Pagination: listings return up to 100 items (default 50) and an opaque nextCursor; pass it back as cursor with the same folder, sort and query. Import items page with after and nextAfter.

Every error uses one envelope. Quote the request ID when you contact support.

Error
{  "error": {    "code": "insufficient_scope",    "message": "This API key doesn't have the scope this request needs.",    "retryable": false,    "requestId": "6f1c2c1e-0d55-4c1b-9a51-2d0f3b8f5e21"  }}
StatusCodeMeaning
401invalid_api_keyThis API key isn't valid. Check the Authorization header or create a new key.
403api_key_suspendedThis API key is suspended because the person it acts for can no longer manage keys here. An owner can reassign it.
403insufficient_scopeThis API key doesn't have the scope this request needs.
403forbiddenYou don't have permission to do that.
404not_foundWe couldn't find that.
400invalid_inputCheck the highlighted fields and try again.
400invalid_idempotency_keyReload the page and try again.
409idempotency_conflictThis request conflicts with an earlier one. Reload and try again.
412revision_conflictThis changed since you opened it. Reload and try again.
409name_takenA folder with that name already exists here.
409quota_exceededThis workspace has reached its storage limit.
409upload_incompleteSome of the file hasn't arrived yet. Resume the upload and try again.
410upload_expiredThis upload expired. Start it again.
429rate_limitedToo many attempts. Try again later.
503api_not_readyWe're finishing an update. Try again in a minute.
503unavailableSomething 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.

Create a folder
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"}'
Rename, then delete
# 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.

Single PUT
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 '{}'
Parts and resume
# 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.

Download a file
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 answer preparing first: repeat the request with the same intentId until it's ready.
  • Password-protected items answer locked. Keys never bypass passwords; unlock them in a browser instead.
  • Other states: not_found, retired, paused, expired (start again) and failed with 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.

Status and pause
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 Bearer token answers 429 until the window resets.
  • Over a limit you get 429 rate_limited with Retry-After in seconds. Storage quota still applies: quota_exceeded is 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.