Reference

Admin API

Every endpoint under /_/api/admin/*, grouped by purpose.

The admin UI and GitOps integrations talk to this surface. All mutation routes require a session cookie (issued by POST /_/api/admin/login). Sessions are IP-bound — a token is rejected from a different source IP — and default to a 4-hour TTL (DGP_SESSION_TTL_HOURS).

Endpoints documented here are admin only. The S3-compatible API lives under / and is documented by AWS themselves.

Authentication and session#

MethodPathPurpose
POST/_/api/admin/loginBootstrap password → session cookie
POST/_/api/admin/login-asLog in as an IAM user (access_key_id + secret_access_key)
POST/_/api/admin/logoutEnd the current session
GET/_/api/admin/session{valid, admin_gui}
POST/_/api/admin/session/browser-connectIssue a limited browser-lift session for an IAM non-admin (S3 browse only)
POST/_/api/admin/session/open-browser-connectBrowser-lift session when authentication: none
GET/_/api/whoami{mode, version, user, external_providers}
POST/_/api/admin/recover-dbReset the config DB when the bootstrap hash doesn't match (public, rate-limited)
PUT/_/api/admin/passwordChange the bootstrap password — re-encrypts the SQLCipher DB atomically

Configuration — three scopes#

All three scopes route through the same apply_config_transition path, so hot-reload semantics are identical no matter which level you use.

Field-level (legacy GUI forms)#

MethodPathPurpose
GET/_/api/admin/configRuntime config as flat JSON
PUT/_/api/admin/configPartial JSON update

Section-level#

MethodPathBodyPurpose
GET/_/api/admin/config/section/:name[?format=yaml]Section slice as JSON or YAML
PUT/_/api/admin/config/section/:nameRFC 7396 JSON Merge PatchPartial section update
POST/_/api/admin/config/section/:name/validatesame as PUTDry-run: {ok, warnings[], diff, requires_restart}

:nameadmission / access / storage / advanced. Unknown names → 404.

Merge-patch semantics: keys missing from the body are preserved; null deletes; objects merge recursively. Secrets round-trip (GET → edit → PUT never clears credentials).

Document-level (GitOps)#

MethodPathBodyPurpose
GET/_/api/admin/config/export[?section=<name>]Canonical YAML (secrets redacted)
GET/_/api/admin/config/declarative-iam-exportProject current DB IAM into access: YAML fragment (for declarative GitOps seeding; see declarative-iam.md)
POST/_/api/admin/config/declarative-iam-validate{yaml: <access fragment>}Dry-run the declarative IAM reconcile (diff_iam preview, zero DB writes)
POST/_/api/admin/config/declarative-iam-apply{yaml: <access fragment>}Atomic single-transaction IAM reconcile from the YAML fragment
GET/_/api/admin/config/defaults[?section=<name>]JSON Schema (for YAML LSP and Monaco)
POST/_/api/admin/config/validate{yaml: <doc>}Dry-run full-document apply
POST/_/api/admin/config/section/:name/validate{<section-body>}Dry-run section apply; in declarative mode warns with diff_iam preview (see declarative-iam.md)
POST/_/api/admin/config/apply{yaml: <doc>}Atomic full-document apply + persist
POST/_/api/admin/config/tracesynthetic request bodyEvaluate against the admission chain
GET/_/api/admin/config/trace?method=&path=&...Query-param variant (bookmarkable trace URLs)
POST/_/api/admin/config/sync-nowForce an immediate config-DB pull from the sync bucket

Full-document apply returns {applied, persisted, requires_restart, warnings, persisted_path}. Persist failure returns HTTP 500, not 200+warning — GitOps pipelines can't mistake a half-applied state for a clean success.

CLI wrapper:

export DGP_BOOTSTRAP_PASSWORD=...
deltaglider_proxy config apply deltaglider_proxy.yaml --server https://s3.acme.example

Backends#

MethodPathPurpose
GET/_/api/admin/backendsList named backends
POST/_/api/admin/backendsCreate; validates S3 creds upfront
DELETE/_/api/admin/backends/:nameRemove — refuses to delete the default or in-use backends
POST/_/api/admin/test-s3Test an arbitrary S3 connection without persisting
GET / POST/_/api/admin/bucketsList bucket origins / create a bucket on a backend
POST/_/api/admin/buckets/:bucket/migrateMove a bucket's data to another backend as a durable, write-gated job — see Jobs

IAM (gated by iam_mode)#

POST/PUT/DELETE return 403 { "error": "iam_declarative" } when access.iam_mode: declarative. Reads stay open for diagnostics.

MethodPathPurpose
GET / POST/_/api/admin/usersList / create
PUT / DELETE/_/api/admin/users/:idUpdate / delete
POST/_/api/admin/users/:id/rotate-keysRotate access keys
POST/_/api/admin/users/:id/cloneClone a user (new keys, copied permissions)
GET / POST/_/api/admin/groupsList / create
PUT / DELETE/_/api/admin/groups/:idUpdate / delete
POST/_/api/admin/groups/:id/cloneClone a group
POST/_/api/admin/groups/:id/membersAdd user to group
DELETE/_/api/admin/groups/:id/members/:user_idRemove user from group
GET/_/api/admin/iam/versionMonotonic IAM-index rebuild counter for deterministic diagnostics/tests
GET/_/api/admin/policiesList canned policy templates (public, no session)

External auth (OAuth / OIDC)#

MethodPathPurpose
GET / POST/_/api/admin/ext-auth/providersList / create
PUT / DELETE/_/api/admin/ext-auth/providers/:idUpdate / delete
POST/_/api/admin/ext-auth/providers/:id/testProbe the .well-known endpoint
GET / POST/_/api/admin/ext-auth/mappingsList / create group mapping rules
PUT / DELETE/_/api/admin/ext-auth/mappings/:idUpdate / delete
POST/_/api/admin/ext-auth/mappings/previewPreview which groups a given identity would be assigned
GET/_/api/admin/ext-auth/identitiesList external identities (read-only, not gated)
POST/_/api/admin/ext-auth/sync-membershipsRe-evaluate mapping rules and sync group memberships
GET/_/api/admin/ext-auth/versionMonotonic external-auth rebuild counter (sibling of iam/version) for deterministic diagnostics/tests
POST/_/api/admin/migrateMigrate legacy bootstrap creds into an IAM user

OAuth redirect flow (public, no session)#

MethodPathPurpose
GET/_/api/admin/oauth/authorize/:providerKick off OAuth (PKCE, state, nonce)
GET/_/api/admin/oauth/callbackProvider callback → issue session cookie

Full Backup#

MethodPathPurpose
GET/_/api/admin/backupExport zip (manifest + config + IAM + secrets)
POST/_/api/admin/backupImport — atomic; all parts sha256-verified before any state change. Gated by iam_mode.

Response on POST carries per-resource counters: {users_created, users_skipped, groups_created, groups_skipped, memberships_created, external_identities_created, external_identities_skipped}.

external_identities are remapped through the imported user + provider ID maps. Orphaned records (user or provider didn't import) are dropped with a WARN log.

Legacy JSON-only import path is still supported for pre-v0.8.4 scripts.

Diagnostics and usage#

MethodPathPurpose
POST/_/api/admin/usage/scanTrigger a prefix-size scan
GET/_/api/admin/usageRead the cached usage tree
GET/_/api/admin/usage/bucket/:bucketO(1) running usage counter for one bucket: {object_count, logical_bytes, stored_bytes, savings_percentage, last_scan_at, never_scanned}. Maintained inline on every PUT/DELETE — no scan. Per-instance; approximate across a fleet.
POST/_/api/admin/usage/refresh?bucket=XUncapped full scan of the bucket → overwrite the counter with ground truth. The one remaining O(n) path; reconciles drift or seeds a never-scanned bucket. Returns the refreshed counter row.
GET/_/api/admin/deltaspace/savingsPer-prefix reference-aware delta savings (30s in-memory cache)
GET/_/api/admin/diagnostics/delta-efficiencyCached delta-efficiency report for a bucket's deltaspaces
POST/_/api/admin/diagnostics/delta-efficiency/scanTrigger a delta-efficiency scan
POST/_/api/admin/diagnostics/delta-efficiency/verifyVerify reconstructed objects against stored deltas
GET/_/api/admin/diagnostics/scan[/status]Integrity-scan status (per-bucket or all-buckets map)
POST/_/api/admin/diagnostics/scan/start / /stopStart / stop a background integrity scan
GET/_/api/admin/diagnostics/scan/streamSSE stream of live scan progress
GET/_/api/admin/audit[?limit=N]Snapshot of the in-memory audit ring, newest first. Bounded (default 500, override DGP_AUDIT_RING_SIZE). Stdout tracing::info! is still the long-term audit source.
GET/_/api/admin/logs[?level=&target=&q=&limit=N]Filtered backlog of the in-memory operational-log ring (INFO+ floor), newest first. Bounded (DGP_LOG_RING_SIZE, default 2000).
GET/_/api/admin/logs/stream[?level=&target=&q=]Live tail of operational logs via server-sent events, same filters as the backlog.
GET/_/api/admin/event-outbox[?status=failed&limit=N&offset=N&sort=occurred_at&order=desc]Paged durable object-event outbox rows plus status counts. Delivery is background-only; delivered rows default to 24h/10,000-row retention; see event-outbox.md.
POST/_/api/admin/event-outbox/:id/requeueRequeue a single failed outbox row for re-delivery
POST/_/api/admin/event-outbox/requeueBulk-requeue failed outbox rows
GET / PUT / DELETE/_/api/admin/session/s3-credentialsPer-session S3 credential store for the browse panel

Object operations (browse panel)#

Server-side helpers behind the embedded S3 browser's bulk actions.

MethodPathPurpose
GET/_/api/admin/objects/listList all keys under a bucket/prefix
POST/_/api/admin/objects/copyServer-side copy of selected objects
POST/_/api/admin/objects/moveServer-side move (copy + delete)
POST/_/api/admin/objects/deleteBulk delete selected objects
GET/_/api/admin/objects/zipStream selected objects as a ZIP

Jobs — one surface for everything background#

Replication rules, lifecycle rules, and one-off maintenance jobs (re-encrypt, bucket migration) share a single read+action API. Job ids are namespaced: replication:<rule>, lifecycle:<rule>, maintenance:<n>. Rules stay YAML-authoritative under storage.replication.rules[] / storage.lifecycle.rules[]; maintenance one-offs are DB-born. See replication.md and lifecycle.md for rule shapes and guardrails.

MethodPathPurpose
GET/_/api/admin/jobsEvery job as one normalized row: kind, scope, status (idle / queued / running / cancelling / succeeded / failed / cancelled), pause flag, progress, last run.
GET/_/api/admin/jobs/:id/runs?limit=NRecent runs, newest first. A maintenance one-off synthesizes a single run — the job IS its run.
GET/_/api/admin/jobs/:id/failures?limit=NRecent per-object failures, newest first.
POST/_/api/admin/jobs/:id/pause / /resumeReplication and lifecycle rules. Persists across restarts.
POST/_/api/admin/jobs/:id/run-nowReplication and lifecycle rules. Synchronous; 409 when paused or already leased.
POST/_/api/admin/jobs/:id/previewLifecycle only — dry-run candidate keys. Read-only: no deletes, no history rows.
POST/_/api/admin/jobs/:id/cancelMaintenance only — cancel a queued or running one-off. A pre-flip migrate cancel unwinds cleanly.
POST/_/api/admin/jobs/reencrypt{"buckets": [...]} (max 100) → one durable re-encrypt job per bucket: {started: [{bucket, job_id}], errors: [...]}.
POST/_/api/admin/buckets/:bucket/migrate{"target_backend": "...", "delete_source": false}202 Accepted + {job_id, id: "maintenance:<n>", bucket, from_backend, to_backend}.
GET/_/api/admin/jobs/bucket/:bucketThe bucket's active maintenance job, if any — status/phase/counts only, no config detail. Session-light: browser-lift sessions can read it (powers the busy banner in the object browser).

Actions outside a kind's capability matrix return 405 with the supported list. Lifecycle preview is intentionally read-only; scheduler and run-now executions persist history/failure rows in the config DB and use per-rule leases so instances sharing the DB never double-execute.

Write gate: while a re-encrypt or migrate job is active, S3 writes to that bucket return 503 SlowDown (SDKs back off and retry); reads pass untouched. The gate engages at job creation and lifts when the job finishes (for migrations, the moment the bucket flips to the new backend).

Resource limits (env vars)#

VariableDefaultPurpose
DGP_MAX_OBJECT_SIZE100 MiBLargest single object (and, per upload, largest multipart upload).
DGP_MAX_MULTIPART_UPLOADS1000Maximum concurrent multipart uploads across the proxy.
DGP_MAX_TOTAL_MULTIPART_BYTESmax_object_size × max_uploads / 4Global in-flight byte cap across all multipart uploads. Protects against the C3 DoS pattern where many uploads accumulate without completing. Reject with SlowDown when exceeded.
DGP_MULTIPART_IDLE_TTL_HOURS24Idle-TTL for incomplete multipart uploads. The periodic sweeper drops uploads with no UploadPart activity for this long (excluding uploads currently being completed).
DGP_AUDIT_RING_SIZE500In-memory audit ring capacity.
DGP_LOG_RING_SIZE2000In-memory operational-log ring capacity (backs the admin Logs viewer).
DGP_LOG_RING_LEVELinfoMinimum severity captured into the operational-log ring/stream (error/warn/info/debug/trace). Independent of the stdout log level.
DGP_LOG_FORMATtextStdout log format: text (human-readable) or json (one JSON object per line, jq-greppable). Startup-only.
DGP_SESSION_TTL_HOURS4Admin session cookie lifetime.

Keyboard shortcuts (app-wide)#

Reachable via ? anywhere in the UI (when focus is not in an input). is Ctrl on non-Apple platforms.

KeyScopeAction
⌘,GlobalOpen Settings
⌘/GlobalOpen Docs
?GlobalThis shortcuts reference
/ Object browserMove between objects and folders
Enter / Object browserOpen folder / inspect object
/ BackspaceObject browserGo up one folder
Home / EndObject browserJump to first / last row
EscObject browserClose inspector, or go up one folder
⌘KSettingsCommand palette (fuzzy nav + shell actions)
⌘SSettingsApply the currently-visible dirty section
/ + Enter, EscPaletteNavigate / run / close

Operational endpoints (no admin prefix)#

Unauthenticated — needed for load-balancer probes and Prometheus:

MethodPathPurpose
GET/_/healthLiveness — process answers; no backend I/O, no version (anti-fingerprinting)
GET/_/readyReadiness — actually probes the storage backend + config DB; 200 {status:"ready"} or 503. Point LB readiness checks here; use /_/health for liveness.
GET/_/metricsPrometheus text format

/_/ready probes the backend with a bounded, retried ListBuckets so a brief provider latency spike doesn't flip readiness to a paging 503: it only reports not-ready if every attempt fails. Tune with DGP_READY_TIMEOUT_SECS (per-attempt, default 3) and DGP_READY_RETRIES (extra attempts, default 2) — raise the timeout for a storage provider with a long tail latency, raise retries to ride out short blips.

Session-protected (reveals per-bucket sizes):

MethodPathPurpose
GET/_/statsAggregate storage stats from the O(1) per-bucket counter (no scan, no object cap). 10s cache on the all-buckets aggregate; ?bucket=NAME reads one bucket uncached.