Metadata REST API
The metadata server exposes a REST API alongside its RPC protocol. The REST endpoints serve utilities and monitoring. All query parameters use kebab-case (e.g., volume-id, base-path). Each endpoint responds only to the HTTP method(s) shown below; a request to a valid path with an unsupported method receives 405 Method Not Allowed.
Versioning
Section titled “Versioning”Every endpoint below is served at both its versioned path (/v1/status) and its bare path (/status); omitting the slug implies v1. The versioned form is canonical — use it in new integrations, since a future v2 will change what the bare path resolves to. The /metrics endpoint is exempt and is only ever served unversioned, because Prometheus expects that exact scrape path.
The CLI utilities request the versioned path first and retry the bare path if the server answers 404, so a newer find.flexfs, analyze.flexfs, or dedup.flexfs still works against a metadata server predating the slug.
Common Query Parameters
Section titled “Common Query Parameters”These parameters are accepted by most endpoints:
| Parameter | Type | Description |
|---|---|---|
vid / volume-id | string | Volume UUID or name. The two spellings are equivalent, and either accepts a UUID or a volume name; a name is resolved to its UUID before the request runs, and either form is checked against the volumes this server has open — an identifier matching none of them, including a well-formed UUID, is rejected with 400. Required by most endpoints; the volume-keyed and maintenance endpoints make it optional (omit = all open volumes). |
base-path | string | Base32-encoded base path to scope the query |
mount-path | string | The client’s mount point (plain text, e.g. /mnt/flexfs). Used to interpret symbolic links whose targets are absolute paths — see below. |
at-time | uint64 | Point-in-time in nanoseconds since epoch (for time-travel mounts) |
pretty | bool | On JSON endpoints, pretty=true indents the response (default is compact). No effect on text endpoints. |
Error responses
Section titled “Error responses”A failed request carries its reason as a plain-text body, so a client can show it rather than just a status line:
400— the caller’s request is at fault: an unparsable, out-of-range, or unrecognized query parameter, abase-paththat does not resolve, or a volume identifier matching no open volume. The body names the cause, e.g.validation error: invalid limit parameter: must be between 1 and 1000orvalidation error: no such volume: "archive".500— the server failed. The body is empty and the reason is written to the metadata server’s log, since it is not something the caller can act on.
Symbolic links in base-path
Section titled “Symbolic links in base-path”Endpoints that accept base-path (/find, /duplicates, and the three /analyze/* endpoints) follow symbolic links found among its components, including the final one, as long as the link target stays within the volume. Links with relative targets (such as ../data) resolve on their own. Links with absolute targets (such as /mnt/flexfs/data) are written in the client’s namespace, so the server needs the mount-path parameter to translate them; without it, or when the target falls outside the mount, the request fails with 400 Bad Request and a message naming the link. The CLI utilities supply mount-path automatically when run from inside a mount.
Endpoints
Section titled “Endpoints”Analyze Files
Section titled “Analyze Files”GET /v1/analyze/filesReturns the top files by size, size_bin, or cost within a volume or subtree.
| Parameter | Type | Default | Description |
|---|---|---|---|
vid / volume-id | string | Volume UUID or name (required) | |
base-path | string | Base32-encoded path prefix | |
mount-path | string | Client mount point, for resolving absolute symbolic-link targets | |
at-time | uint64 | Point-in-time (nanoseconds since epoch) | |
header | bool | false | Include column headers |
limit | uint32 | 100 | Maximum results (1-1000) |
order-by | string | size | Sort field: size, size_bin, or cost |
Response: 200 OK with tab-separated text, one file per line.
Analyze Folders
Section titled “Analyze Folders”GET /v1/analyze/foldersReturns per-directory recursive size and cost.
| Parameter | Type | Default | Description |
|---|---|---|---|
vid / volume-id | string | Volume UUID or name (required) | |
base-path | string | Base32-encoded path prefix | |
mount-path | string | Client mount point, for resolving absolute symbolic-link targets | |
at-time | uint64 | Point-in-time (nanoseconds since epoch) | |
header | bool | false | Include column headers |
max-depth | uint32 | 0 | Maximum depth (0 = unlimited) |
Response: 200 OK with tab-separated text.
Analyze Users
Section titled “Analyze Users”GET /v1/analyze/usersReturns per-user size and cost.
| Parameter | Type | Default | Description |
|---|---|---|---|
vid / volume-id | string | Volume UUID or name (required) | |
base-path | string | Base32-encoded path prefix | |
mount-path | string | Client mount point, for resolving absolute symbolic-link targets | |
at-time | uint64 | Point-in-time (nanoseconds since epoch) | |
header | bool | false | Include column headers |
limit | uint32 | 100 | Maximum results (1-1000) |
order-by | string | cost | Sort field: size or cost |
Response: 200 OK with tab-separated text (UID, size, cost).
GET /v1/findSearches the filesystem with filters on inode attributes.
| Parameter | Type | Description |
|---|---|---|
vid / volume-id | string | Volume UUID or name (required) |
base-path | string | Base32-encoded path prefix |
mount-path | string | Client mount point, for resolving absolute symbolic-link targets |
fields | string | Comma-separated output fields |
header | bool | Include column headers |
name | string | Dentry name filter (supports glob) |
type | string | Inode type: b, c, d, f, l, p, s |
uid | uint32 | UID filter |
gid | uint32 | GID filter |
ino | uint64 | Inode number filter |
limit | uint64 | Maximum number of results (0 or omit for unlimited) |
empty | bool | Empty file/directory filter |
sparse | bool | Sparse file filter |
perm | string | Required permission bits (octal, with or without a leading 0 — 755 and 0755 mean the same thing; max 07777). Matches when (mode & perm-mask) == perm. |
perm-mask | string | Bits of the file mode to test (octal, with or without a leading 0; default 0777, max 07777, must be non-zero) |
min-size, max-size | uint64 | Byte size range |
min-blocks, max-blocks | uint64 | Block count range |
min-cost, max-cost | float64 | Monthly cost range |
min-nlink, max-nlink | uint32 | Hard link count range |
min-depth, max-depth | uint32 | Depth range |
min-atime, max-atime | uint64 | Access time range (seconds since epoch) |
min-btime, max-btime | uint64 | Birth time range (seconds since epoch) |
min-ctime, max-ctime | uint64 | Change time range (seconds since epoch) |
min-mtime, max-mtime | uint64 | Modification time range (seconds since epoch) |
min-size-bin, max-size-bin | uint32 | Size bin range (0-74) |
Response: 200 OK with tab-separated text.
Find Old
Section titled “Find Old”GET /v1/find-oldLists non-directory files whose access time is older than a cutoff — useful for identifying stale data. The Find endpoint is more capable and covers the same need via its max-atime filter.
| Parameter | Type | Default | Description |
|---|---|---|---|
vid / volume-id | string | Volume UUID or name (required) | |
co | int64 | 2 years ago | Cutoff access time, in seconds since epoch. Files with atime older than this are returned. |
lim | uint64 | 100000 | Maximum number of results |
Response: 200 OK with one line per file in the form size:atime:btime:base32path, where size is the effective size (min(size, blksize × blocks)) and atime/btime are seconds since epoch.
Duplicates
Section titled “Duplicates”GET /v1/duplicatesReturns groups of files that share the same size and block count, which are candidates for deduplication.
| Parameter | Type | Description |
|---|---|---|
vid / volume-id | string | Volume UUID or name (required) |
base-path | string | Base32-encoded path prefix |
mount-path | string | Client mount point, for resolving absolute symbolic-link targets |
at-time | uint64 | Point-in-time (nanoseconds since epoch) |
limit | uint64 | Maximum number of duplicate groups (0 or omit for unlimited). When set, the largest groups by file size are retained. |
min-size, max-size | uint64 | Byte size range |
min-blocks, max-blocks | uint64 | Block count range |
Response: 200 OK with a JSON array of group objects, ordered by size descending then blocks descending. Each group has size (uint64), blocks (uint64), and files (array of {path, ino, nlink, btime} objects). Multiple paths for the same inode (hard links) are included as separate entries. When limit is set, only the largest groups are returned.
GET /v1/locksLists the volume’s currently held file locks.
| Parameter | Type | Description |
|---|---|---|
vid / volume-id | string | Volume UUID or name (required) |
Response: 200 OK with a JSON array of active lock objects (empty array if the volume holds no locks).
Sessions
Section titled “Sessions”GET /v1/sessionsReturns the client sessions currently connected to the server.
| Parameter | Type | Description |
|---|---|---|
vid / volume-id | string | Volume UUID or name. Omit to report every volume. |
Response: 200 OK — a JSON object keyed by volume ID; each value is that volume’s array of session objects (empty if it has no clients). A specified volume yields just that one; omitting it yields all. Each session object:
| Field | Type | Description |
|---|---|---|
id | string | Session UUID |
remote_addr | string | Client remote address |
local_addr | string | Server-side local address |
build_version | string | Client build version |
proto_version | string | Negotiated protocol version |
command | string | Client command line |
kernel | string | Client kernel version |
cpus | uint32 | Client CPU count |
ram | uint64 | Client RAM in bytes |
uid | uint32 | Client user ID |
gid | uint32 | Client group ID |
started | string | Session start time |
blocks_read | uint64 | Blocks read this reporting period |
blocks_written | uint64 | Blocks written this reporting period |
bytes_read | uint64 | Bytes read this reporting period |
bytes_written | uint64 | Bytes written this reporting period |
Fields with zero or empty values are omitted. The read/write counters reflect the current reporting period and reset periodically.
Size Bins
Section titled “Size Bins”GET /v1/size-binsReturns the distribution of files across logarithmic size bins.
| Parameter | Type | Description |
|---|---|---|
vid / volume-id | string | Volume UUID or name. Omit to report every open volume. |
Response: 200 OK — a JSON object keyed by volume ID; each value maps size-bin index (0–74) to file count. A specified volume yields just that one; omitting it yields all open volumes.
Missing Objects
Section titled “Missing Objects”GET /v1/missing-objectsScans block keys and reports any whose backing object is absent from the block store. Each key is verified with a HEAD against the block store.
| Parameter | Type | Default | Description |
|---|---|---|---|
vid / volume-id | string | Volume UUID or name. Omit to scan every open volume. | |
live-only | bool | false | Restrict the scan to live block keys |
retired-only | bool | false | Restrict the scan to retired block keys (mutually exclusive with live-only) |
concurrency | int | 16 | Number of parallel HEAD requests. The default matches the metadata server’s block-store concurrency ceiling; higher values queue on that limit rather than adding parallelism. |
Response: 200 OK — a JSON object keyed by volume ID; each value is that volume’s array of entries (empty if none). A specified volume yields just that one; omitting it yields all open volumes. Each entry has kind (missing or error), ino, idx, retired, key, and error (error entries only). In all-volumes mode, a volume whose scan fails (e.g. one already running) is logged and skipped rather than failing the request.
| Status | Meaning |
|---|---|
200 | Scan ran; results returned keyed by volume ID. |
400 | An invalid parameter: unresolvable volume, live-only and retired-only both set, or a malformed concurrency/boolean value. |
409 | A single-volume scan was requested and one is already in progress for it. |
500 | A single-volume scan failed. |
Verify
Section titled “Verify”GET /v1/verifyRuns a read-only integrity check of a volume’s metadata (attr, block-key, and dentry scans plus counter comparisons). This endpoint never modifies data; use the offline CLI (meta.flexfs verify --fix) to repair.
| Parameter | Type | Description |
|---|---|---|
vid / volume-id | string | Volume UUID or name. Omit to verify all open volumes. |
Response: 200 OK — a JSON object keyed by volume ID; each value is that volume’s VerifyReport (per-scan counts plus an overall ok boolean). A specified volume yields just that one; omitting it yields all open volumes.
Metrics
Section titled “Metrics”GET /metricsReturns Prometheus-format metrics for the metadata server.
Status
Section titled “Status”GET /v1/statusReturns server health status.
Maintenance
Section titled “Maintenance”Backup
Section titled “Backup”POST /v1/backupAuthorization: Bearer $META_TOKENWrites a consistent, point-in-time checkpoint of one or all volumes into a staging directory while the server keeps running. The staging directory is safe to rsync offsite. See Maintenance → Online backup for the recommended workflow.
$META_TOKEN is the metadata server’s token (the UUID configured with meta.flexfs init creds).
| Parameter | Type | Default | Description |
|---|---|---|---|
dest | string | <dbFolder>/.backup | Staging directory for the checkpoint. Must be on the same filesystem as --dbFolder for hard-linking, and must not be or contain the data directory. |
vid / volume-id | string | A single volume UUID or name. Omit to checkpoint all open volumes. | |
blocking | bool | false | Wait for the checkpoint and return a summary. See the maintenance note above. |
Response: with blocking=true, 200 OK and body checkpointed N volume(s) (the count distinguishes a real backup from a no-op, e.g. zero open volumes). Otherwise 202 Accepted, no body.
| Status | Meaning |
|---|---|
200 | (blocking=true) Checkpoint completed; body reports the number of volumes written. |
202 | Checkpoint started in the background. |
400 | The vid / volume-id value could not be resolved to a volume. |
401 | Missing or invalid bearer token. |
409 | (async only) A conflicting checkpoint is already in progress. |
500 | Checkpoint failed, or (either mode) dest is or contains the data directory. |
Example — an automated backup that must fail loudly waits for completion (-f fails on non-2xx; self-signed cert, so -k):
curl -fk -X POST \ -H "Authorization: Bearer $META_TOKEN" \ "https://<meta-host>:443/v1/backup?blocking=true"Compact
Section titled “Compact”POST /v1/compactAuthorization: Bearer $META_TOKENTriggers a manual compaction of the metadata database for one or all volumes.
| Parameter | Type | Default | Description |
|---|---|---|---|
vid / volume-id | string | A single volume UUID or name. Omit to compact all open volumes. | |
blocking | bool | false | Wait for the compaction and return a summary. See the maintenance note above. |
Response: with blocking=true, 200 OK and body compacted N volume(s). Otherwise 202 Accepted, no body.
| Status | Meaning |
|---|---|
200 | (blocking=true) Compaction completed; body reports the number of volumes compacted. |
202 | Compaction started in the background. |
400 | The vid / volume-id value could not be resolved to a volume. |
401 | Missing or invalid bearer token. |
409 | (async only) A conflicting compaction is already in progress. |
500 | (blocking=true) Compaction failed. |
POST /v1/scrubAuthorization: Bearer $META_TOKENRetires leaked past-EOF block keys for one or all volumes.
| Parameter | Type | Default | Description |
|---|---|---|---|
vid / volume-id | string | A single volume UUID or name. Omit to scrub all open volumes. | |
blocking | bool | false | Wait for the scrub and return a summary. See the maintenance note above. |
Response: with blocking=true, 200 OK and body retired N leaked block key(s): M inode(s) affected, K volume(s) skipped. Otherwise 202 Accepted, no body.
| Status | Meaning |
|---|---|
200 | (blocking=true) Scrub completed; body reports the counts. |
202 | Scrub started in the background. |
400 | The vid / volume-id value could not be resolved to a volume. |
401 | Missing or invalid bearer token. |
409 | (async only) A conflicting scrub is already in progress. |
500 | (blocking=true) Scrub failed. |
Reconcile
Section titled “Reconcile”POST /v1/reconcileAuthorization: Bearer $META_TOKENChecks that every object in the block store is accounted for in the metadata store and removes any that are not.
| Parameter | Type | Default | Description |
|---|---|---|---|
vid / volume-id | string | A single volume UUID or name. Omit to reconcile all open volumes. When set, objects belonging to other volumes sharing the same block store are left untouched. | |
blocking | bool | false | Wait for the reconciliation and return a summary. See the maintenance note above. |
Response: with blocking=true, 200 OK and body reconciled N object(s): M deleted, K marked for deletion. Otherwise 202 Accepted, no body.
| Status | Meaning |
|---|---|
200 | (blocking=true) Reconciliation completed; body reports the counts. |
202 | Reconciliation started in the background. |
400 | The vid / volume-id value could not be resolved to a volume. |
401 | Missing or invalid bearer token. |
409 | (async only) A conflicting reconciliation is already in progress. |
Delete Locks
Section titled “Delete Locks”DELETE /v1/locksAuthorization: Bearer $META_TOKENDeletes held file locks for a volume, optionally scoped to a single inode — the DELETE verb on the /v1/locks resource that GET lists.
| Parameter | Type | Default | Description |
|---|---|---|---|
vid / volume-id | string | Volume UUID or name (required) | |
ino | uint64 | 0 | Inode to scope the deletion to. 0 or omitted deletes all locks in the volume. |
Response: 200 OK with a plain-text body deleted N lock(s).
| Status | Meaning |
|---|---|
200 | Locks deleted; body reports the count. |
400 | Missing/unresolvable volume, or an invalid ino. |
401 | Missing or invalid bearer token. |
500 | Deletion failed. |