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

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/"
}

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/"}'

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

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
  },
  "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
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.

Example:

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

GET /system/health

Public health check endpoint. No authentication required. Returns only a minimal status object – no detailed internal checks are exposed.

Response: 200 OK

{
  "status": "ok",
  "version": "0.9.0"
}

The response contains only status (either "ok" or "degraded") and version (the server version string). For detailed system diagnostics, 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

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.

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": 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": true
}
FieldTypeRequiredDescription
providerstringYesMust be "smtp"
hoststringYesSMTP server hostname
portintegerYesSMTP server port
usernamestringYesSMTP username
passwordstringYesSMTP password
from_addressstringYesSender email address
from_namestringNoSender display name
tlsbooleanNoEnable TLS (default: true)

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.