SA Storage

Guide

External API

Use scoped API tokens for scripts, CI/CD, and backends. Each token belongs to a service and can only touch that service's files under its key prefix.

Quick start

  1. Create a service — e.g. “Blog CDN” with prefix blog/.
  2. Create an API token for it with the access it needs.
  3. Copy the secret — it's shown once — and store it as SA_TOKEN.
  4. Check what the token can do:
shell
curl -s -H "Authorization: Bearer $SA_TOKEN" "$SA_URL/api/me"

Services & prefixes

A service is an app or API source. Every file and token belongs to exactly one. Each service has a default key prefix; uploads are stored under it so services never overwrite each other.

The effective prefixof a token is its own override if set, otherwise its service's prefix. If an upload key already starts with the prefix it isn't doubled: with prefix blog/, both hero.png and blog/hero.png are stored at blog/hero.png.

Authentication

Send the token in the Authorization header on every request:

http
Authorization: Bearer sa_xxxxxxxxxxxxxxxx

Tokens are stored as SHA-256 hashes and can't be recovered. Lost one? Rotate it from the dashboard to get a new secret without changing its permissions.

Permissions

PermissionAllows
(any token)GET /api/me, GET /api/services (own service)
files:listGET /api/files
files:readGET /api/files/<key>
files:writePOST /api/files, PUT /api/files/<key>, PATCH /api/files/<key>
files:deleteDELETE /api/files/<key> (also required to move a file)
stats:readGET /api/stats (own token and service only)

Token, service, and reconcile management stay dashboard-only.

Files

List

shell
curl -s -H "Authorization: Bearer $SA_TOKEN" \
  "$SA_URL/api/files?prefix=images/"

Responses page with cursor; pass it back to get the next page.

Upload

shell
curl -X POST -H "Authorization: Bearer $SA_TOKEN" \
  -F "file=@./report.pdf" \
  -F "key=documents/report.pdf" \
  "$SA_URL/api/files"

Replace contents

shell
curl -X PUT -H "Authorization: Bearer $SA_TOKEN" \
  -H "Content-Type: application/pdf" \
  --data-binary @./report-v2.pdf \
  "$SA_URL/api/files/blog/documents/report.pdf"

Move or rename

shell
curl -X PATCH -H "Authorization: Bearer $SA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"key": "blog/archive/report.pdf"}' \
  "$SA_URL/api/files/blog/documents/report.pdf"

Needs files:write and files:delete. Returns 409 if the destination exists — add "overwrite": true to replace it. Sessions can also send serviceId to relabel a file.

Delete

shell
curl -X DELETE -H "Authorization: Bearer $SA_TOKEN" \
  "$SA_URL/api/files/blog/documents/report.pdf"
URL-encode each path segment of keys that contain spaces or special characters.

Statistics

GET /api/stats returns totals, percent shares by token, service, and route, a gap-filled time series, and the 25 most recent requests. Tokens only see their own traffic.

shell
curl -s -H "Authorization: Bearer $SA_TOKEN" \
  "$SA_URL/api/stats?from=2026-09-01T00:00:00Z&tz=Europe/Paris"

Series buckets are hourly for windows up to 2 days, daily up to ~13 months, then monthly — in the tz time zone (default UTC).

Public URLs

Every object is readable without authentication at /files/<key>, with ETag, Last-Modified, and a one-hour cache. Add ?download to force a download.

Anyone with the URL can read the file. Don't store secrets in the bucket.

Errors

Errors are JSON: { "error": "Human-readable message" }

  • 400Invalid input or key
  • 401Missing, invalid, or expired token or session
  • 403Missing permission, key outside the token prefix, or file owned by another service
  • 404Object, service, or token not found
  • 409Conflict — destination exists, or service still has files/tokens
  • 429Too many failed sign-in attempts
  • 500Server, database, or R2 error

Managing tokens

  • One token per integration, named after where it runs.
  • Grant the smallest access preset that works; add an expiry for temporary access.
  • Rotate on a schedule or after a leak — the old secret stops working instantly.
  • Watch Last used and the Statistics page for unexpected activity.