Admin REST API
A JSON API for operating the server, mounted at /rest/api/v1. It powers the web console and is equally usable from scripts.
Authentication
Plain headers carrying an access key the server knows (root or any active IAM user):
bash
curl -H "X-API-Key: admin" -H "X-API-Secret: admin-secret" \
http://localhost:8000/rest/api/v1/server/infoEndpoints
Server
| Endpoint | Method | Returns |
|---|---|---|
/server/info | GET | name, version, uptime, processors, memory, and the running edition label (community for a cluster of ≤2 nodes, enterprise when license.enterprise: true is set for a larger cluster) with its serverPools / rackAwareness capability flags (both always true — single edition, no feature gating) |
/server/stats | GET | bucket and object counts |
Bucket quotas
Hard per-bucket size limits (bytes). A PUT, multipart complete, or copy that would push the bucket over its quota is refused with HTTP 507 QuotaExceeded; no quota (0) means unlimited.
Enforcement is best-effort: each node checks against a short-lived (≈5s) per-node usage cache, so in a multi-node cluster concurrent writes may overshoot the quota slightly within that window, and a delete frees headroom only after the cache refreshes. It is a guardrail against runaway growth, not a byte-exact accounting boundary.
| Endpoint | Method | Purpose |
|---|---|---|
/buckets/{bucket}/quota | GET | current {quota, usage} in bytes |
/buckets/{bucket}/quota | PUT | set the quota — body {"quota": <bytes>} |
/buckets/{bucket}/quota | DELETE | clear the quota (unlimited) |
Custom response headers (TinyS3 extension)
Not an S3 feature. Real S3 has no per-bucket response-header configuration — this is a TinyS3 convenience, additive and harmless to standard S3 clients.
Configure a set of extra HTTP headers that TinyS3 adds to successful object GET/HEAD responses for the bucket. The driving use case is serving assets into a cross-origin-isolated page, e.g. Cross-Origin-Resource-Policy: cross-origin and Cross-Origin-Embedder-Policy: require-corp.
The config is a JSON object of header-name → value. A header is only added when the response does not already carry it (existing headers are never overridden or duplicated).
Guardrails (enforced on PUT with a 400, and again at apply time by silently skipping the bad entry so a misconfiguration never fails an object read):
- Header name must match
^[A-Za-z0-9-]{1,64}$. - No CR/LF or other control characters in name or value; value capped at 1024 chars.
- Reserved names are rejected (case-insensitive):
content-length,content-type,content-range,content-encoding,transfer-encoding,connection,keep-alive,etag,last-modified,accept-ranges,date,host,authorization,www-authenticate, and any name starting withx-amz-. - At most 32 headers; total JSON capped at 16 KiB.
| Endpoint | Method | Purpose |
|---|---|---|
/buckets/{bucket}/response-headers | GET | current header map ({} if none) |
/buckets/{bucket}/response-headers | PUT | set the headers — body is a JSON object, e.g. {"Cross-Origin-Resource-Policy":"cross-origin"} |
/buckets/{bucket}/response-headers | DELETE | clear all custom headers |
Remote endpoints
Saved remote S3 endpoints (name + URL + credentials) reused by transfer jobs and bucket-replication rules. Managed at /endpoints (GET list, POST add, DELETE by id). Endpoints are persisted across restarts with encrypted credentials; responses never include the secret.
Replication
| Endpoint | Method | Purpose |
|---|---|---|
/buckets/{bucket}/replication/resync | POST | re-queue the bucket's source objects against its replication rules, skipping objects already matching the destination (DR resync); returns the count enqueued |
Root-only. See the bucket replication guide.
Metrics
| Endpoint | Method | Returns |
|---|---|---|
/metrics | GET | in-memory counters since start: requests, bytes up/down, disk I/O, auth failures by reason, requests by operation/status, latency per operation, and 15-minute time series |
Prometheus text format is served separately at /tinys3/metrics.
Audit logging
Root-only. Turn per-request audit logging on/off and change its targets at runtime (persisted, no restart). See the audit configuration.
| Endpoint | Method | Purpose |
|---|---|---|
/audit | GET | status: {enabled, file, bucket, bucketPrefix, retentionDays, deliveryIntervalSeconds} — the retention/interval fields report the effective values the sink runs at |
/audit | PUT | set {enabled, file?, bucket?, bucketPrefix?, retentionDays?, deliveryIntervalSeconds?}; a bucket target must already have versioning + object lock enabled or the call is rejected |
/audit/tail | GET | live tail of the most recent audit entries (in-memory ring); ?since=<seq>&limit=<n> returns {enabled, cursor, entries[]} — poll with the returned cursor as the next since |
/audit/archive | GET | paginated listing of the WORM archive bucket's objects; ?startAfter=<key>&maxKeys=<n> → {bucket, prefix, objects[], nextStartAfter, truncated} (objects marked manifest are seal objects) |
/audit/archive/object | GET | ?key=<key> returns one archived object's parsed records {key, records[]}; the key must be within the configured audit prefix |
IP access control
Root-only front-door lists enforced on the resolved client IP (socket peer unless the request comes from a configured security.trustedProxies hop) before routing or auth, for both S3 and admin requests. A block (deny) always wins over an allow; loopback always passes so a local operator can never be locked out. See self-lockout recovery.
| Endpoint | Method | Purpose |
|---|---|---|
/blocklist | GET | { entries[], callerIp } — active bans (single IPs or CIDRs) plus your resolved IP |
/blocklist | POST | ban { cidr, reason?, ttlSeconds?, force? }; force required to ban 0.0.0.0/0 |
/blocklist | DELETE | ?cidr=<cidr> removes a ban |
/allowlist | GET | { enabled, entries[], callerIp } — enabled is the enforcement flag (default-deny when true) |
/allowlist | POST | allow { cidr, reason?, ttlSeconds? } |
/allowlist | DELETE | ?cidr=<cidr> removes an entry |
/allowlist | PUT | { enabled, force? } toggle enforcement — refused unless your own resolved IP is covered (and the list is non-empty) or force=true, so you can't lock yourself out |
Kill-switches: TINY_S3_BLOCKLIST_DISABLED / TINY_S3_ALLOWLIST_DISABLED (or security.blocklistDisabled / security.allowlistDisabled).
Erasure & healing
| Endpoint | Method | Purpose |
|---|---|---|
/erasure/health | GET | disks healthy/total, shard scheme, object health counts |
/erasure/disks | GET | per-disk status |
/erasure/resilience | GET | tolerated-failure summary |
/erasure/healing/stats | GET | scans run, objects healed/unrecoverable, last scan |
/erasure/healing/scan | POST | trigger a healing scan now |
Transfer jobs
One API for every data transfer — a one-off copy and a continuous sync are the same job with a different intervalSeconds (0 = run once, N = repeat every N seconds).
| Endpoint | Method | Purpose |
|---|---|---|
/jobs | GET / POST | list / create (create starts the job immediately) |
/jobs/{id} | GET / PUT / DELETE | inspect / edit / remove |
/jobs/{id}/start · /cancel | POST | control (start re-runs a cancelled/paused job) |
/jobs/{id}/pause · /resume | POST | control (scheduled jobs only) |
/jobs/{id}/reconcile | POST | verify destination holds the whole source (PULL and PUSH) |
/jobs/test-connection | POST | validate a remote endpoint + credentials |
A job has a direction (PULL or PUSH), one remote endpoint, and one of two scopes: a mapping (sourceBucket/destBucket + optional prefixes) or a mirror (buckets list or allBuckets, with optional deleteOrphans). Responses carry per-cycle progress (objects/bytes, failures, throughput, ETA) and cumulative stats (cycles, copied/skipped/deleted totals) for scheduled jobs.
Completion is verified. A one-off transfer is not declared COMPLETED from a clean copy pass alone — on finishing, the engine reconciles the destination against the source (object count, per-object size, and single-part ETag). If gaps remain it runs one more copy pass to fill them and re-verifies; the job is COMPLETED only when the source fully reconciles, otherwise COMPLETED_WITH_ERRORS. The result is returned as a verification report on the job (inSync, sourceObjects, matched, missing, sizeMismatch, sample discrepancies), so a migration that silently dropped an object is caught rather than reported as done.
Restart-safe. Jobs are persisted; a transfer interrupted by a restart is re-queued and resumed on boot (already-copied objects are skipped by the size/ETag check, so re-driving is safe).
Credentials & users
| Endpoint | Method | Purpose |
|---|---|---|
/endpoints | GET / POST | saved remote endpoints for transfer jobs |
/users | GET | local users (console pickers) |
Notes
- Endpoints that only exist in a given mode (e.g.
/erasure/*in simple mode) return404; the console degrades gracefully. - All responses are JSON. Errors carry
{ "error": ... }or{ "message": ... }.