Metadata REST API (v1)
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”Version 1 is served at /v1/... and at the bare, unversioned path: /v1/find and /find are the same endpoint. A path carrying no version slug always means version 1, so the bare paths belong to this version and are never answered by version 2, which is served only at /v2/....
The /status and /metrics endpoints carry no version slug at all. /status is the health check that container probes, load balancers and monitoring point at, and /metrics is the scrape path Prometheus expects. They belong to no version and are always served. /status has the same spelling on the admin and free servers, which serve no /metrics.
A client asks for one version and receives an answer about that version; the server does not translate between them.
Enabling version 1
Section titled “Enabling version 1”The metadata server does not serve version 1 by default. It is served alongside version 2 only when the server is started with the deprecated v1 routes enabled.
Without that, every URL on this page except /status and /metrics answers 404 Not Found — the version 1 routes are not registered at all, so they are absent rather than refused. This includes the bare, unversioned paths, since those are version 1. Version 2 is served either way.
flexfs_meta_rest_ops_total carries an api_version label, which shows whether anything is still calling version 1 — worth checking before you stop enabling it:
sum by (api_version) (rate(flexfs_meta_rest_ops_total[5m]))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 or out-of-range query parameter, abase-paththat does not resolve, a backupdestthat is or contains the db folder, 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". Query parameters an endpoint does not recognize are ignored.401— an authenticated maintenance endpoint (such as backup, compact, or scrub) was called without a valid metadata-server token. The unauthenticated query endpoints never return this.403— an API access rule refused the caller’s source address, before any handler ran. Only a server whose--accessFileexists and whose rules do not admit that address answers this; a server with no access file, which is the default, never does. Rules name an endpoint by its path template with the version slug removed —/locksfor/v1/locks. See API Access Control.500— the server failed, and the reason is written to the metadata server’s log. The body is empty, except from/backup,/compact,/scrub,/recount/all,/recount/blocks-retired,/missing-objectsandDELETE /locks, which return the error text.503— a query that reports or filters on cost could not obtain the billing rates from the admin server, or the request was for a volume that has stopped accepting changes after a metadata read error: a find, find-old, analyze, duplicates, size bins or missing objects request, a request that needed to change it, or a verify request that hit a read error. For the first, the body beginscost unavailableand aRetry-After: 5header is set; for the second, the body beginsvolume unavailable after a metadata read errorand aRetry-After: 60header is set; a find or find-old response already under way is cut off instead. A query, verify or missing objects request still running when the metadata server shuts down gets the bodymetadata server is shutting downand aRetry-After: 30header; a response already under way is cut off instead, so the client sees an incomplete transfer rather than a complete one. A blocking backup, compaction, recount or scrub request still running at shutdown may get the same response or have its connection closed. Retry.
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 | unlimited | Maximum depth; 0 returns the root row only |
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 |
at-time | uint64 | Point-in-time (nanoseconds since epoch) |
fields | string | Comma-separated output fields, written in the order given: atime, blocks, btime, cost, ctime, gid, ino, mode, mtime, nlink, path, perm, size, size_bin, type, uid, xattrs. Default path. |
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 (must be non-zero) |
limit | uint64 | Maximum number of results (0 or omitted = unlimited) |
empty | bool | true matches directories with no entries and regular files with no allocated blocks (including sparse files that have a size but no data); false matches the opposite. Other file types never match. With at-time, directories are judged as of that time. |
sparse | bool | Sparse file filter (false matches non-sparse files). Regular files only. |
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; bits outside perm-mask are rejected. |
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 (regular files only) |
min-blocks, max-blocks | uint64 | Block count range (regular files only) |
min-cost, max-cost | float64 | Monthly cost range (regular files only) |
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-127, regular files only) |
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 (0 = unlimited) |
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 regular files that share the same size and block count, which are candidates for deduplication. Files with no allocated blocks are never included, and a group needs at least two distinct inodes. Every Find filter is accepted except type; the parameters below are the ones the dedup.flexfs utility uses.
| 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 omitted = 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. A specified volume yields just that one, with an empty array if it has no clients; omitting it yields every volume that has at least one client. Each session object:
| Field | Type | Description |
|---|---|---|
id | string | Session UUID |
remote_addr | string | Client remote address |
local_addr | string | The client’s own address, as the client reports it |
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 volume’s billed storage distributed across activity-based size bins. Each bin is a 30-day age tier reflecting how recently the files’ data was used: bin 0 holds data active within the last 30 days, bin 1 data 30–60 days old, and so on up to bin 127.
| 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–127) to the total billed size, in bytes, of the files whose data falls in that tier (sparse files counted at their allocated size). A specified volume yields just that one; omitting it yields all open volumes except any that have stopped accepting changes after a metadata read error.
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 reported with a single error entry (with ino and idx of 0 and no key) 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. In all-volumes mode, a volume whose check fails, such as one whose metadata cannot be read, is reported with ok set to false and the reason in error rather than failing the request.
Metrics
Section titled “Metrics”GET /metricsReturns Prometheus-format metrics for the metadata server.
Status
Section titled “Status”GET /statusReturns server health status. Unversioned: it is served whichever API versions this server serves, and there is no /v1/status.
Maintenance
Section titled “Maintenance”Backup
Section titled “Backup”POST /v1/backupAuthorization: Bearer <meta-token>Writes 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 not be or contain the db folder. Place it on the same filesystem as --dbFolder so database files are hard-linked; on another filesystem they are copied, which takes longer and uses the full space. |
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, or dest is or contains the db folder. |
401 | Missing or invalid bearer token. |
409 | (async only) A conflicting checkpoint is already in progress. |
500 | Checkpoint failed. |
503 | The server is shutting down. A blocking backup running when the server shuts down may return this status with a Retry-After: 30 header or have its connection closed. |
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-addr>/v1/backup?blocking=true"Compact
Section titled “Compact”POST /v1/compactAuthorization: Bearer <meta-token>Triggers 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. |
503 | The server is shutting down. A blocking compaction running when the server shuts down may return this status with a Retry-After: 30 header or have its connection closed. |
POST /v1/scrubAuthorization: Bearer <meta-token>Retires 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. |
503 | The server is shutting down. A blocking scrub running when the server shuts down may return this status with a Retry-After: 30 header or have its connection closed. |
Reconcile
Section titled “Reconcile”POST /v1/reconcileAuthorization: Bearer <meta-token>Checks that every object in the block store is accounted for in the metadata store and removes any that are not. An object is removed only when it is named like a block of one of the server’s volumes, is at least 48 hours old, and no live or retired block key refers to it. If the metadata for an object cannot be read, the object is kept and the error is logged. A block store is skipped while any volume that uses it has stopped accepting changes after a metadata read error.
| 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. |
503 | (async only) The server is shutting down. |
Recount All
Section titled “Recount All”POST /v1/recount/allAuthorization: Bearer <meta-token>Recounts a volume’s stored totals (inodes, directory entries, blocks, retired blocks, and size) from its metadata and saves the corrected values while the server keeps running. These totals feed the volume metrics and usage reports. Use it when Verify reports countsOK: false.
| Parameter | Type | Default | Description |
|---|---|---|---|
vid / volume-id | string | A single volume UUID or name. Omit to recount all open volumes. | |
blocking | bool | false | Wait for the recount and return the results. See the maintenance note above. |
Response: with blocking=true, 200 OK with a JSON object keyed by volume ID; each value has after and before objects holding the totals after and before the recount: inode, dentry, block, blockRetired, and size. When vid is omitted, retired volumes and volumes whose recount fails are left out, and failures are logged. Otherwise 202 Accepted, no body.
| Status | Meaning |
|---|---|
200 | (blocking=true) Recount completed; body reports the totals. |
202 | Recount started in the background. |
400 | The vid / volume-id value could not be resolved to a volume, or names a retired volume. |
401 | Missing or invalid bearer token. |
409 | (async only) A conflicting recount is already in progress. |
500 | (blocking=true) Recount failed. |
503 | The server is shutting down, or (blocking=true) the volume has stopped accepting changes after a metadata read error, with a Retry-After: 60 header. |
Recount Retired Blocks
Section titled “Recount Retired Blocks”POST /v1/recount/blocks-retiredAuthorization: Bearer <meta-token>Recounts only a volume’s retired block total from its metadata and saves the corrected value while the server keeps running. It takes less time than Recount All on a large volume. Use it when Verify reports that only blockRetired differs between storedCounts and actualCounts.
| Parameter | Type | Default | Description |
|---|---|---|---|
vid / volume-id | string | A single volume UUID or name. Omit to recount all open volumes. | |
blocking | bool | false | Wait for the recount and return the results. See the maintenance note above. |
Response: with blocking=true, 200 OK with a JSON object keyed by volume ID; each value has after and before, the retired block total after and before the recount. When vid is omitted, volumes whose recount fails are left out, and failures are logged. Otherwise 202 Accepted, no body.
| Status | Meaning |
|---|---|
200 | (blocking=true) Recount completed; body reports the totals. |
202 | Recount 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 recount is already in progress. |
500 | (blocking=true) Recount failed. |
503 | The server is shutting down, or (blocking=true) the volume has stopped accepting changes after a metadata read error, with a Retry-After: 60 header. |
Delete Locks
Section titled “Delete Locks”DELETE /v1/locksAuthorization: Bearer <meta-token>Deletes 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. |