Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

System Operations

Administrative endpoints for garbage collection, background tasks, cron scheduling, metrics, health checks, backup/restore, and user/group management. Most system endpoints require root access.

Endpoint Summary

Garbage Collection

MethodPathDescriptionRoot Required
POST/system/gcRun synchronous garbage collectionYes

Background Tasks

MethodPathDescriptionRoot Required
POST/system/tasks/reindexTrigger a reindex taskYes
POST/system/tasks/gcTrigger a background GC taskYes
GET/system/tasksList all tasks with progressYes
GET/system/tasks/{id}Get a single taskYes
DELETE/system/tasks/{id}Cancel a taskYes

Cron Scheduling

MethodPathDescriptionRoot Required
GET/system/cronList cron schedulesYes
POST/system/cronCreate a cron scheduleYes
PATCH/system/cron/{id}Update a cron scheduleYes
DELETE/system/cron/{id}Delete a cron scheduleYes

Lifecycle Configuration

MethodPathDescriptionRoot Required
GET/system/lifecycleRead lifecycle policyYes
PUT/system/lifecycleReplace lifecycle policyYes

Backup & Restore

MethodPathDescriptionRoot Required
POST/versions/exportExport database as .aeordbYes
POST/versions/diffCreate patch between versionsYes
POST/versions/importImport a backup or patchYes
POST/versions/promotePromote a version hash to HEADYes

Monitoring

MethodPathDescriptionRoot Required
GET/system/statsSystem stats (JSON)Yes (auth required)
GET/system/metricsPrometheus metricsYes (auth required)
GET/system/healthHealth checkNo (public)

API Key Management

MethodPathDescriptionRoot Required
POST/auth/keys/adminCreate an API keyYes
GET/auth/keys/adminList all API keysYes
DELETE/auth/keys/admin/{key_id}Revoke an API keyYes

User Management

MethodPathDescriptionRoot Required
POST/system/usersCreate a userYes
GET/system/usersList all usersYes
GET/system/users/{user_id}Get a userYes
PATCH/system/users/{user_id}Update a userYes
DELETE/system/users/{user_id}Deactivate a user (soft delete)Yes

Group Management

MethodPathDescriptionRoot Required
POST/system/groupsCreate a groupYes
GET/system/groupsList all groupsYes
GET/system/groups/{name}Get a groupYes
PATCH/system/groups/{name}Update a groupYes
DELETE/system/groups/{name}Delete a groupYes

Email Configuration

MethodPathDescriptionRoot Required
GET/system/email-configGet email configuration (secrets masked)Yes
PUT/system/email-configSave email configuration (SMTP or OAuth)Yes
POST/system/email-testSend a test emailYes

Garbage Collection

POST /system/gc

Run garbage collection synchronously. Identifies and removes orphaned entries not reachable from the current HEAD.

Query Parameters:

ParameterTypeDefaultDescription
dry_runbooleanfalseIf true, report what would be collected without deleting

Response: 200 OK

The response contains GC statistics (entries scanned, reclaimed bytes, etc.).

Example:

# Dry run
curl -X POST "http://localhost:6830/system/gc?dry_run=true" \
  -H "Authorization: Bearer $TOKEN"

# Actual GC
curl -X POST http://localhost:6830/system/gc \
  -H "Authorization: Bearer $TOKEN"

Error Responses:

StatusCondition
403Non-root user
500GC failure

Background Tasks

POST /system/tasks/reindex

Enqueue a reindex task for a directory path. Re-scans all files and rebuilds index entries.

Request Body:

{
  "path": "/data/",
  "force": false,
  "metadata_only": false,
  "index_flush_writes": 262144,
  "index_flush_ms": 30000
}
FieldTypeDefaultDescription
pathstringRequiredDirectory path to reindex
forcebooleanfalseWhen true, also migrates older live FileRecord payloads in the requested subtree to the current version before indexing eligible files. Omit or set to false for index-only reprocessing. Internal/system FileRecords can be migrated but are not indexed.
metadata_onlybooleanfalseWhen true, rebuild only virtual @ metadata indexes from FileRecord metadata. This skips file body reads, JSON parsing, and parser plugins.
index_flush_writesinteger262144Flush buffered index mutations after this many field/strategy updates.
index_flush_msinteger30000Flush buffered index mutations after this many milliseconds.

Response: 200 OK

{
  "id": "task-uuid-here",
  "task_type": "reindex",
  "status": "pending"
}

Example:

curl -X POST http://localhost:6830/system/tasks/reindex \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"path": "/data/", "metadata_only": true}'

POST /system/tasks/gc

Enqueue a background GC task (non-blocking).

Request Body:

{
  "dry_run": false
}

Response: 200 OK

{
  "id": "task-uuid-here",
  "task_type": "gc",
  "status": "pending"
}

Example:

curl -X POST http://localhost:6830/system/tasks/gc \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"dry_run": false}'

GET /system/tasks

List all tasks with their current progress.

Response: 200 OK

{
  "items": [
    {
      "id": "task-uuid-here",
      "task_type": "reindex",
      "status": "running",
      "args": {"path": "/data/"},
      "progress": 0.45,
      "eta_ms": 1775968500000
    }
  ]
}

Each task includes progress (0.0-1.0) and eta_ms (estimated completion timestamp) if available.

Example:

curl http://localhost:6830/system/tasks \
  -H "Authorization: Bearer $TOKEN"

GET /system/tasks/

Get a single task by ID.

Response: 200 OK

{
  "id": "task-uuid-here",
  "task_type": "reindex",
  "status": "running",
  "args": {"path": "/data/"},
  "progress": 0.45,
  "eta_ms": 1775968500000
}

Error Responses:

StatusCondition
404Task not found

DELETE /system/tasks/

Cancel a task.

Response: 200 OK

{
  "id": "task-uuid-here",
  "status": "cancelled"
}

Example:

curl -X DELETE http://localhost:6830/system/tasks/task-uuid-here \
  -H "Authorization: Bearer $TOKEN"

Cron Scheduling

Tip: The portal Settings page provides an intuitive UI for scheduling garbage collection. Navigate to Settings → Garbage Collector to configure.

GET /system/cron

List all cron schedules.

Response: 200 OK

{
  "items": [
    {
      "id": "nightly-gc",
      "schedule": "0 2 * * *",
      "task_type": "gc",
      "args": {"dry_run": false},
      "enabled": true
    }
  ]
}

POST /system/cron

Create a new cron schedule.

Request Body:

{
  "id": "nightly-gc",
  "schedule": "0 2 * * *",
  "task_type": "gc",
  "args": {"dry_run": false},
  "enabled": true
}
FieldTypeRequiredDescription
idstringYesUnique schedule identifier
schedulestringYesCron expression
task_typestringYesTask type to enqueue ("gc", "reindex", "backup")
argsobjectYesArguments passed to the task
enabledbooleanYesWhether the schedule is active

Response: 201 Created

Example:

curl -X POST http://localhost:6830/system/cron \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "nightly-gc",
    "schedule": "0 2 * * *",
    "task_type": "gc",
    "args": {"dry_run": false},
    "enabled": true
  }'

Error Responses:

StatusCondition
400Invalid cron expression
409Schedule with this ID already exists

PATCH /system/cron/

Update a cron schedule. All fields are optional – only provided fields are changed.

Request Body:

{
  "enabled": false,
  "schedule": "0 3 * * *"
}
FieldTypeDescription
enabledbooleanEnable or disable the schedule
schedulestringNew cron expression
task_typestringNew task type
argsobjectNew task arguments

Response: 200 OK

Returns the updated schedule.

Error Responses:

StatusCondition
400Invalid cron expression
404Schedule not found

DELETE /system/cron/

Delete a cron schedule.

Response: 200 OK

{
  "id": "nightly-gc",
  "deleted": true
}

Error Responses:

StatusCondition
404Schedule not found

Lifecycle Configuration

Lifecycle configuration is stored inside the database at /.aeordb-config/lifecycle.json. Missing fields use safe defaults, so older databases that only have snapshot_retention continue to allow snapshot writes.

GET /system/lifecycle

Return the current lifecycle policy.

Response: 200 OK

{
  "snapshot_writes_enabled": true,
  "snapshot_retention": {
    "auto_months": 0,
    "manual_months": 0
  }
}

PUT /system/lifecycle

Replace the lifecycle policy.

Request Body:

{
  "snapshot_writes_enabled": false,
  "snapshot_retention": {
    "auto_months": 1,
    "manual_months": 12
  }
}
FieldTypeDefaultDescription
snapshot_writes_enabledbooleantrueAllow creation of new snapshot records. When false, existing snapshots can still be listed, read, restored, deleted, exported, and pruned.
snapshot_retention.auto_monthsinteger0Months after which auto snapshots are eligible for pruning. 0 disables pruning.
snapshot_retention.manual_monthsinteger0Months after which manual snapshots are eligible for pruning. 0 disables pruning.

When snapshot_writes_enabled is false, manual POST /versions/snapshots and snapshot rename requests return 403. Automatic safety snapshots, such as file-restore and pre-GC snapshots, are skipped; the underlying operation continues when it can safely proceed without writing a new snapshot.


Backup & Restore

POST /versions/export

Export the database (or a specific version) as an .aeordb archive file.

Query Parameters:

ParameterTypeDescription
snapshotstringExport a named snapshot (default: HEAD)
hashstringExport a specific version by hex hash

Response: 200 OK

  • Content-Type: application/octet-stream
  • Content-Disposition: attachment; filename="export-{hash_prefix}.aeordb"
  • Body: binary archive data

Example:

# Export HEAD
curl -X POST http://localhost:6830/versions/export \
  -H "Authorization: Bearer $TOKEN" \
  -o backup.aeordb

# Export a specific snapshot
curl -X POST "http://localhost:6830/versions/export?snapshot=v1.0" \
  -H "Authorization: Bearer $TOKEN" \
  -o backup-v1.aeordb

# Export by hash
curl -X POST "http://localhost:6830/versions/export?hash=a1b2c3d4..." \
  -H "Authorization: Bearer $TOKEN" \
  -o backup.aeordb

POST /versions/diff

Create a patch file representing the difference between two versions.

Query Parameters:

ParameterTypeRequiredDescription
fromstringYesSource snapshot name or hex hash
tostringNoTarget snapshot name or hex hash (default: HEAD)

Response: 200 OK

  • Content-Type: application/octet-stream
  • Content-Disposition: attachment; filename="patch-{hash_prefix}.aeordb"
  • Body: binary patch data

Example:

curl -X POST "http://localhost:6830/versions/diff?from=v1.0&to=v2.0" \
  -H "Authorization: Bearer $TOKEN" \
  -o patch-v1-v2.aeordb

POST /versions/import

Import a backup or patch file. Body limit: 10 MB.

Query Parameters:

ParameterTypeDefaultDescription
forcebooleanfalseForce import even if conflicts exist
promotebooleanfalsePromote the imported version to HEAD

Request:

  • Headers:
    • Authorization: Bearer <token> (required)
  • Body: raw .aeordb file bytes

Response: 200 OK

{
  "status": "success",
  "backup_type": "export",
  "entries_imported": 1500,
  "chunks_imported": 3200,
  "files_imported": 450,
  "directories_imported": 30,
  "deletions_applied": 5,
  "version_hash": "a1b2c3d4e5f6...",
  "head_promoted": true
}

Example:

curl -X POST "http://localhost:6830/versions/import?promote=true" \
  -H "Authorization: Bearer $TOKEN" \
  --data-binary @backup.aeordb

Error Responses:

StatusCondition
400Invalid or corrupt backup file
403Non-root user

POST /versions/promote

Promote an arbitrary version hash to HEAD.

Query Parameters:

ParameterTypeRequiredDescription
hashstringYesHex-encoded version hash to promote

Response: 200 OK

{
  "status": "success",
  "head": "a1b2c3d4e5f6..."
}

Example:

curl -X POST "http://localhost:6830/versions/promote?hash=a1b2c3d4e5f6..." \
  -H "Authorization: Bearer $TOKEN"

Error Responses:

StatusCondition
400Invalid hash format
404Version hash not found in storage

Monitoring

GET /system/stats

System statistics endpoint. Returns a structured JSON snapshot of all engine metrics. All values are read from O(1) atomic counters — this endpoint is safe to poll frequently with no performance impact.

Response: 200 OK

{
  "identity": {
    "version": "0.9.0",
    "database_path": "/data/mydb.aeordb",
    "hash_algorithm": "Blake3_256",
    "chunk_size": 262144,
    "node_id": 1,
    "uptime_seconds": 86400
  },
  "counts": {
    "files": 150000,
    "directories": 23000,
    "symlinks": 500,
    "chunks": 420000,
    "snapshots": 12,
    "forks": 2
  },
  "sizes": {
    "disk_total": 2147483648,
    "kv_file": 86114304,
    "logical_data": 1800000000,
    "chunk_data": 1200000000,
    "void_space": 5242880,
    "dedup_savings": 600000000
  },
  "throughput": {
    "writes_per_sec": { "1m": 42.3, "5m": 38.1, "15m": 35.7, "peak_1m": 120.0 },
    "reads_per_sec": { "1m": 156.2, "5m": 140.5, "15m": 138.0, "peak_1m": 450.0 },
    "bytes_written_per_sec": { "1m": 435200, "5m": 392000, "15m": 367000 },
    "bytes_read_per_sec": { "1m": 16065536, "5m": 14450000, "15m": 14200000 }
  },
  "latency": {
    "write": { "p50": 5.6, "p95": 15.4, "p99": 20.5 },
    "read": { "p50": 2.1, "p95": 8.3, "p99": 12.0 },
    "query": { "p50": 4.2, "p95": 22.0, "p99": 45.0 },
    "flush": { "p50": 1.2, "p95": 5.0, "p99": 12.0 }
  },
  "health": {
    "disk_usage_percent": 48.5,
    "kv_fill_ratio": 0.72,
    "dedup_hit_rate": 0.33,
    "gc_last_reclaimed_bytes": 1048576,
    "write_buffer_depth": 42
  },
  "memory": {
    "process": {
      "rss_bytes": 2147483648,
      "peak_rss_bytes": 3221225472,
      "virtual_bytes": 8589934592,
      "data_bytes": 1610612736,
      "swap_bytes": 0,
      "thread_count": 32,
      "fd_count": 128
    },
    "index_cache": {
      "cached_indexes": 16,
      "dirty_indexes": 4,
      "deleted_indexes": 0,
      "pending_mutations": 512,
      "total_mutations": 102400,
      "flushes": 7,
      "flushed_indexes": 92,
      "entries": 2500000,
      "values": 350000,
      "estimated_bytes": 734003200,
      "top_cached_indexes": []
    },
    "directory_cache": {
      "entries": 12000,
      "estimated_bytes": 16777216
    },
    "caches": {
      "permissions_entries": 128,
      "index_config_entries": 16,
      "grants_index_entries": 4
    },
    "estimated_engine_owned_bytes": 750780416
  },
  "sync": {
    "active_peers": 2,
    "failing_peers": 0,
    "last_sync_ms": 1776563922032,
    "sync_lag_entries": { "peer_2": 0, "peer_3": 15 }
  }
}

Response sections:

SectionDescription
identityServer version, database path, hash algorithm, chunk size, node ID, and uptime
countsCurrent totals for files, directories, symlinks, chunks, snapshots, and forks
sizesByte-level storage breakdown: disk total, KV file size, logical data, chunk data, void space, dedup savings
throughputRolling read/write rates (1m, 5m, 15m averages) and peak rates
latencyPercentile latencies (p50, p95, p99) for write, read, query, and flush operations (in milliseconds)
healthOperational health signals: disk usage, KV fill ratio, dedup hit rate, last GC reclamation, write buffer depth
memoryProcess memory and AeorDB-owned cache diagnostics, including RSS, swap, thread/fd counts, index cache estimates, directory cache estimates, and cache entry counts
syncReplication status: active/failing peers, last sync timestamp, per-peer sync lag (only present when replication is active)

Note: The previous GET /system/stats returned a flat object computed via O(n) iteration. The new response is structured into nested sections and is O(1) — no performance concerns polling at high frequency.

sizes.logical_data is the sum of live file sizes reachable from the current HEAD tree. sizes.chunk_data is the stored payload size of unique chunk entries in the KV index, initialized from entry metadata without reading chunk bodies. sizes.void_space is tracked reusable space inside the append log; it is not filesystem free space.

memory.process is sampled from the operating system. On Linux this uses /proc/self/status plus /proc/self/fd; on macOS it uses Mach task information plus /dev/fd. Platform-specific fields that are unavailable are reported as 0. memory.index_cache.estimated_bytes, memory.directory_cache.estimated_bytes, and memory.estimated_engine_owned_bytes are best-effort estimates intended for diagnosis and trend monitoring; they are not allocator-exact byte accounting.

Example:

curl http://localhost:6830/system/stats \
  -H "Authorization: Bearer $TOKEN"

GET /system/health

Public health check endpoint. No authentication required. Once the database is ready, this returns only a minimal status object – no detailed internal checks are exposed.

Response: 200 OK

{
  "status": "healthy",
  "version": "0.9.5"
}

During startup, clean opens, dirty startup, or WAL/KV recovery, AeorDB binds HTTP before the storage engine is ready. In that state, /system/health still returns 200 OK with startup progress:

{
  "status": "starting",
  "phase": "rebuild_kv_scan",
  "message": "Scanning WAL entries for dirty startup recovery",
  "version": "0.9.5",
  "progress": 0.42,
  "eta": {
    "seconds": 480,
    "at": "2026-06-12T03:05:00Z"
  }
}

progress is an overall startup fraction from 0.0 to 1.0. eta is null when unknown, or an object with estimated seconds remaining and an RFC 3339 timestamp. Non-health routes return 503 Service Unavailable until the full application router is ready.

For detailed system diagnostics after startup, use GET /system/stats instead (requires authentication).

Example:

curl http://localhost:6830/system/health

GET /system/metrics

Prometheus-format metrics endpoint. Requires authentication.

Response: 200 OK

  • Content-Type: text/plain; version=0.0.4; charset=utf-8
  • Body: Prometheus text exposition format

Memory gauges are updated when /system/metrics is rendered and by the periodic metrics pulse:

MetricDescription
aeordb_process_rss_bytesCurrent process resident set size
aeordb_process_peak_rss_bytesPeak process resident set size observed by the OS
aeordb_process_virtual_bytesProcess virtual address size
aeordb_process_data_bytesProcess data/heap segment size where available
aeordb_process_swap_bytesProcess swap usage where available
aeordb_process_thread_countProcess thread count where available
aeordb_process_fd_countOpen file descriptor count where available
aeordb_engine_memory_estimated_bytesEstimated AeorDB-owned cache memory tracked by diagnostics
aeordb_index_cache_estimated_bytesEstimated shared index cache memory
aeordb_index_cache_cached_indexesCached field/strategy index count
aeordb_index_cache_dirty_indexesCached indexes with unflushed mutations
aeordb_index_cache_pending_mutationsPending index mutations in the shared buffer
aeordb_index_cache_entriesIndexed scalar entry count currently cached
aeordb_index_cache_valuesRaw indexed value count currently cached
aeordb_directory_cache_estimated_bytesEstimated directory content cache memory
aeordb_directory_cache_entriesDirectory content cache entry count

Example:

curl http://localhost:6830/system/metrics \
  -H "Authorization: Bearer $TOKEN"

API Key Management

POST /auth/keys/admin

Create a new API key. The plaintext key is returned only once – store it securely. Requires root.

Request Body:

{
  "user_id": "550e8400-e29b-41d4-a716-446655440000"
}
FieldTypeRequiredDescription
user_idstring (UUID)NoUser to create the key for (defaults to the calling user)

Response: 201 Created

{
  "key_id": "660e8400-e29b-41d4-a716-446655440001",
  "api_key": "aeor_660e8400_a1b2c3d4e5f6...",
  "user_id": "550e8400-e29b-41d4-a716-446655440000",
  "created_at": "2026-04-13T10:00:00Z"
}

Example:

curl -X POST http://localhost:6830/auth/keys/admin \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"user_id": "550e8400-e29b-41d4-a716-446655440000"}'

GET /auth/keys/admin

List all API keys (metadata only – no secrets). Requires root.

Response: 200 OK

{
  "items": [
    {
      "key_id": "660e8400-e29b-41d4-a716-446655440001",
      "user_id": "550e8400-e29b-41d4-a716-446655440000",
      "created_at": "2026-04-13T10:00:00Z",
      "is_revoked": false
    }
  ]
}

DELETE /auth/keys/admin/

Revoke an API key. Revoked keys cannot be used to obtain tokens. Requires root.

Response: 200 OK

{
  "revoked": true,
  "key_id": "660e8400-e29b-41d4-a716-446655440001"
}

Error Responses:

StatusCondition
400Invalid key ID format
404API key not found

User Management

POST /system/users

Create a new user. Requires root.

Request Body:

{
  "username": "alice",
  "email": "[email protected]",
  "tags": ["editor", "us-west"]
}
FieldTypeRequiredDescription
usernamestringYesUnique username
emailstringNoUser email address
tagsarray of stringsNoAdmin-assigned tags for group membership queries (default: empty)

Response: 201 Created

{
  "user_id": "550e8400-e29b-41d4-a716-446655440000",
  "username": "alice",
  "email": "[email protected]",
  "is_active": true,
  "tags": ["editor", "us-west"],
  "created_at": 1775968398000,
  "updated_at": 1775968398000
}

GET /system/users

List all users. Requires root.

Response: 200 OK

{
  "items": [
    {
      "user_id": "550e8400-e29b-41d4-a716-446655440000",
      "username": "alice",
      "email": "[email protected]",
      "is_active": true,
      "created_at": 1775968398000,
      "updated_at": 1775968398000
    }
  ]
}

GET /system/users/

Get a single user. Requires root.

Response: 200 OK (same shape as the user object above)

Error Responses:

StatusCondition
400Invalid UUID
404User not found

PATCH /system/users/

Update a user. All fields are optional. Requires root.

Request Body:

{
  "username": "alice_updated",
  "email": "[email protected]",
  "is_active": true,
  "tags": ["editor", "us-west", "senior"]
}

Tags are admin-only – users cannot modify their own tags, preventing privilege escalation through self-assigned group membership.

Response: 200 OK (returns the updated user)


DELETE /system/users/

Deactivate a user (soft delete – sets is_active to false). Requires root.

Response: 200 OK

{
  "deactivated": true,
  "user_id": "550e8400-e29b-41d4-a716-446655440000"
}

Group Management

Groups define path-level access control rules using query-based membership. Users are matched into groups by querying safe fields on their user record, including tags.

Tag-Based Group Membership

When query_field is set to "tags", three special operators are available:

OperatorDescriptionExample
hasUser has this exact tag"query_value": "editor"
has_anyUser has at least one of these tags (comma-separated)"query_value": "editor,admin"
has_allUser has all of these tags (comma-separated)"query_value": "editor,us-west"

Standard operators (eq, gt, etc.) also work with tags – they match against the comma-joined tag string.

POST /system/groups

Create a new group. Requires root.

Request Body:

{
  "name": "editors",
  "default_allow": "/content/*",
  "default_deny": "/system/*",
  "query_field": "tags",
  "query_operator": "has",
  "query_value": "editor"
}
FieldTypeRequiredDescription
namestringYesUnique group name
default_allowstringYesPath pattern for allowed access
default_denystringYesPath pattern for denied access
query_fieldstringYesUser field to query for membership (must be a safe field)
query_operatorstringYesComparison operator
query_valuestringYesValue to match against

Response: 201 Created

{
  "name": "editors",
  "default_allow": "/content/*",
  "default_deny": "/system/*",
  "query_field": "role",
  "query_operator": "eq",
  "query_value": "editor",
  "created_at": 1775968398000,
  "updated_at": 1775968398000
}

GET /system/groups

List all groups. Requires root.

Response: 200 OK (object with items array of group objects)


GET /system/groups/

Get a single group. Requires root.

Error Responses:

StatusCondition
404Group not found

PATCH /system/groups/

Update a group. All fields are optional. Requires root.

Request Body:

{
  "default_allow": "/content/*",
  "query_value": "senior-editor"
}

The query_field value is validated against a whitelist of safe fields. Attempting to use an unsafe field returns a 400 error.

Error Responses:

StatusCondition
400Unsafe query field
404Group not found

DELETE /system/groups/

Delete a group. Requires root.

Response: 200 OK

{
  "deleted": true,
  "name": "editors"
}

Error Responses:

StatusCondition
404Group not found

Email Configuration

AeorDB supports sending email notifications (e.g., when files are shared via POST /files/share). Email can be configured using either SMTP or OAuth providers.

GET /system/email-config

Retrieve the current email configuration. Sensitive fields (passwords, client secrets, refresh tokens) are masked as "--------" in the response, and a "configured": true field is added.

Auth: Root only.

Response: 200 OK

{
  "provider": "smtp",
  "host": "smtp.example.com",
  "port": 587,
  "username": "[email protected]",
  "password": "--------",
  "from_address": "[email protected]",
  "from_name": "AeorDB",
  "tls": "starttls",
  "configured": true
}

PUT /system/email-config

Save email configuration. Supports two provider types: SMTP and OAuth.

Auth: Root only.

SMTP Configuration:

{
  "provider": "smtp",
  "host": "smtp.example.com",
  "port": 587,
  "username": "[email protected]",
  "password": "secret",
  "from_address": "[email protected]",
  "from_name": "AeorDB",
  "tls": "starttls"
}
FieldTypeRequiredDescription
providerstringYesMust be "smtp"
hoststringYesSMTP server hostname
portintegerYesSMTP server port
usernamestringYesSMTP username
passwordstringYesSMTP password
from_addressstringYesSender email address
from_namestringNoSender display name (default: "AeorDB")
tlsstringNoTLS mode: "starttls" (port 587, default), "tls" (implicit TLS, port 465), or "none" (cleartext)

OAuth Configuration:

{
  "provider": "oauth",
  "oauth_provider": "gmail",
  "client_id": "...",
  "client_secret": "...",
  "refresh_token": "...",
  "from_address": "[email protected]",
  "from_name": "AeorDB"
}
FieldTypeRequiredDescription
providerstringYesMust be "oauth"
oauth_providerstringYesOAuth provider: "gmail", "outlook", or "custom"
client_idstringYesOAuth client ID
client_secretstringYesOAuth client secret
refresh_tokenstringYesOAuth refresh token
from_addressstringYesSender email address
from_namestringNoSender display name

Response: 200 OK


POST /system/email-test

Send a test email to verify the current configuration.

Auth: Root only.

Request Body:

{
  "to": "[email protected]"
}

Response: 200 OK

{
  "sent": true,
  "message": "Test email sent successfully"
}

On failure:

{
  "sent": false,
  "error": "Connection refused: smtp.example.com:587"
}

Share Notifications

When files are shared via POST /files/share, email notifications are automatically sent to recipients if email is configured. If email is not configured, sharing still works – notifications are silently skipped.