Skip to content

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.

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.

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]))

These parameters are accepted by most endpoints:

ParameterTypeDescription
vid / volume-idstringVolume 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-pathstringBase32-encoded base path to scope the query
mount-pathstringThe client’s mount point (plain text, e.g. /mnt/flexfs). Used to interpret symbolic links whose targets are absolute paths — see below.
at-timeuint64Point-in-time in nanoseconds since epoch (for time-travel mounts)
prettyboolOn JSON endpoints, pretty=true indents the response (default is compact). No effect on text endpoints.

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, a base-path that does not resolve, a backup dest that 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 1000 or validation 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 --accessFile exists 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 — /locks for /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-objects and DELETE /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 begins cost unavailable and a Retry-After: 5 header is set; for the second, the body begins volume unavailable after a metadata read error and a Retry-After: 60 header 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 body metadata server is shutting down and a Retry-After: 30 header; 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.

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.

GET /v1/analyze/files

Returns the top files by size, size_bin, or cost within a volume or subtree.

ParameterTypeDefaultDescription
vid / volume-idstringVolume UUID or name (required)
base-pathstringBase32-encoded path prefix
mount-pathstringClient mount point, for resolving absolute symbolic-link targets
at-timeuint64Point-in-time (nanoseconds since epoch)
headerboolfalseInclude column headers
limituint32100Maximum results (1-1000)
order-bystringsizeSort field: size, size_bin, or cost

Response: 200 OK with tab-separated text, one file per line.

GET /v1/analyze/folders

Returns per-directory recursive size and cost.

ParameterTypeDefaultDescription
vid / volume-idstringVolume UUID or name (required)
base-pathstringBase32-encoded path prefix
mount-pathstringClient mount point, for resolving absolute symbolic-link targets
at-timeuint64Point-in-time (nanoseconds since epoch)
headerboolfalseInclude column headers
max-depthuint32unlimitedMaximum depth; 0 returns the root row only

Response: 200 OK with tab-separated text.

GET /v1/analyze/users

Returns per-user size and cost.

ParameterTypeDefaultDescription
vid / volume-idstringVolume UUID or name (required)
base-pathstringBase32-encoded path prefix
mount-pathstringClient mount point, for resolving absolute symbolic-link targets
at-timeuint64Point-in-time (nanoseconds since epoch)
headerboolfalseInclude column headers
limituint32100Maximum results (1-1000)
order-bystringcostSort field: size or cost

Response: 200 OK with tab-separated text (UID, size, cost).

GET /v1/find

Searches the filesystem with filters on inode attributes.

ParameterTypeDescription
vid / volume-idstringVolume UUID or name (required)
base-pathstringBase32-encoded path prefix
mount-pathstringClient mount point, for resolving absolute symbolic-link targets
at-timeuint64Point-in-time (nanoseconds since epoch)
fieldsstringComma-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.
headerboolInclude column headers
namestringDentry name filter (supports glob)
typestringInode type: b, c, d, f, l, p, s
uiduint32UID filter
giduint32GID filter
inouint64Inode number filter (must be non-zero)
limituint64Maximum number of results (0 or omitted = unlimited)
emptybooltrue 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.
sparseboolSparse file filter (false matches non-sparse files). Regular files only.
permstringRequired 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-maskstringBits of the file mode to test (octal, with or without a leading 0; default 0777, max 07777, must be non-zero)
min-size, max-sizeuint64Byte size range (regular files only)
min-blocks, max-blocksuint64Block count range (regular files only)
min-cost, max-costfloat64Monthly cost range (regular files only)
min-nlink, max-nlinkuint32Hard link count range
min-depth, max-depthuint32Depth range
min-atime, max-atimeuint64Access time range (seconds since epoch)
min-btime, max-btimeuint64Birth time range (seconds since epoch)
min-ctime, max-ctimeuint64Change time range (seconds since epoch)
min-mtime, max-mtimeuint64Modification time range (seconds since epoch)
min-size-bin, max-size-binuint32Size bin range (0-127, regular files only)

Response: 200 OK with tab-separated text.

GET /v1/find-old

Lists 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.

ParameterTypeDefaultDescription
vid / volume-idstringVolume UUID or name (required)
coint642 years agoCutoff access time, in seconds since epoch. Files with atime older than this are returned.
limuint64100000Maximum 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.

GET /v1/duplicates

Returns 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.

ParameterTypeDescription
vid / volume-idstringVolume UUID or name (required)
base-pathstringBase32-encoded path prefix
mount-pathstringClient mount point, for resolving absolute symbolic-link targets
at-timeuint64Point-in-time (nanoseconds since epoch)
limituint64Maximum number of duplicate groups (0 or omitted = unlimited). When set, the largest groups by file size are retained.
min-size, max-sizeuint64Byte size range
min-blocks, max-blocksuint64Block 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/locks

Lists the volume’s currently held file locks.

ParameterTypeDescription
vid / volume-idstringVolume UUID or name (required)

Response: 200 OK with a JSON array of active lock objects (empty array if the volume holds no locks).

GET /v1/sessions

Returns the client sessions currently connected to the server.

ParameterTypeDescription
vid / volume-idstringVolume 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:

FieldTypeDescription
idstringSession UUID
remote_addrstringClient remote address
local_addrstringThe client’s own address, as the client reports it
build_versionstringClient build version
proto_versionstringNegotiated protocol version
commandstringClient command line
kernelstringClient kernel version
cpusuint32Client CPU count
ramuint64Client RAM in bytes
uiduint32Client user ID
giduint32Client group ID
startedstringSession start time
blocks_readuint64Blocks read this reporting period
blocks_writtenuint64Blocks written this reporting period
bytes_readuint64Bytes read this reporting period
bytes_writtenuint64Bytes written this reporting period

Fields with zero or empty values are omitted. The read/write counters reflect the current reporting period and reset periodically.

GET /v1/size-bins

Returns 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.

ParameterTypeDescription
vid / volume-idstringVolume 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.

GET /v1/missing-objects

Scans 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.

ParameterTypeDefaultDescription
vid / volume-idstringVolume UUID or name. Omit to scan every open volume.
live-onlyboolfalseRestrict the scan to live block keys
retired-onlyboolfalseRestrict the scan to retired block keys (mutually exclusive with live-only)
concurrencyint16Number 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.

StatusMeaning
200Scan ran; results returned keyed by volume ID.
400An invalid parameter: unresolvable volume, live-only and retired-only both set, or a malformed concurrency/boolean value.
409A single-volume scan was requested and one is already in progress for it.
500A single-volume scan failed.
GET /v1/verify

Runs 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.

ParameterTypeDescription
vid / volume-idstringVolume 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.

GET /metrics

Returns Prometheus-format metrics for the metadata server.

GET /status

Returns server health status. Unversioned: it is served whichever API versions this server serves, and there is no /v1/status.

POST /v1/backup
Authorization: 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).

ParameterTypeDefaultDescription
deststring<dbFolder>/.backupStaging 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-idstring""A single volume UUID or name. Omit to checkpoint all open volumes.
blockingboolfalseWait 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.

StatusMeaning
200(blocking=true) Checkpoint completed; body reports the number of volumes written.
202Checkpoint started in the background.
400The vid / volume-id value could not be resolved to a volume, or dest is or contains the db folder.
401Missing or invalid bearer token.
409(async only) A conflicting checkpoint is already in progress.
500Checkpoint failed.
503The 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):

Terminal window
curl -fk -X POST \
-H "Authorization: Bearer <meta-token>" \
"https://<meta-addr>/v1/backup?blocking=true"
POST /v1/compact
Authorization: Bearer <meta-token>

Triggers a manual compaction of the metadata database for one or all volumes.

ParameterTypeDefaultDescription
vid / volume-idstringA single volume UUID or name. Omit to compact all open volumes.
blockingboolfalseWait 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.

StatusMeaning
200(blocking=true) Compaction completed; body reports the number of volumes compacted.
202Compaction started in the background.
400The vid / volume-id value could not be resolved to a volume.
401Missing or invalid bearer token.
409(async only) A conflicting compaction is already in progress.
500(blocking=true) Compaction failed.
503The 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/scrub
Authorization: Bearer <meta-token>

Retires leaked past-EOF block keys for one or all volumes.

ParameterTypeDefaultDescription
vid / volume-idstringA single volume UUID or name. Omit to scrub all open volumes.
blockingboolfalseWait 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.

StatusMeaning
200(blocking=true) Scrub completed; body reports the counts.
202Scrub started in the background.
400The vid / volume-id value could not be resolved to a volume.
401Missing or invalid bearer token.
409(async only) A conflicting scrub is already in progress.
500(blocking=true) Scrub failed.
503The 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.
POST /v1/reconcile
Authorization: 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.

ParameterTypeDefaultDescription
vid / volume-idstringA 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.
blockingboolfalseWait 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.

StatusMeaning
200(blocking=true) Reconciliation completed; body reports the counts.
202Reconciliation started in the background.
400The vid / volume-id value could not be resolved to a volume.
401Missing or invalid bearer token.
409(async only) A conflicting reconciliation is already in progress.
503(async only) The server is shutting down.
POST /v1/recount/all
Authorization: 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.

ParameterTypeDefaultDescription
vid / volume-idstringA single volume UUID or name. Omit to recount all open volumes.
blockingboolfalseWait 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.

StatusMeaning
200(blocking=true) Recount completed; body reports the totals.
202Recount started in the background.
400The vid / volume-id value could not be resolved to a volume, or names a retired volume.
401Missing or invalid bearer token.
409(async only) A conflicting recount is already in progress.
500(blocking=true) Recount failed.
503The server is shutting down, or (blocking=true) the volume has stopped accepting changes after a metadata read error, with a Retry-After: 60 header.
POST /v1/recount/blocks-retired
Authorization: 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.

ParameterTypeDefaultDescription
vid / volume-idstringA single volume UUID or name. Omit to recount all open volumes.
blockingboolfalseWait 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.

StatusMeaning
200(blocking=true) Recount completed; body reports the totals.
202Recount started in the background.
400The vid / volume-id value could not be resolved to a volume.
401Missing or invalid bearer token.
409(async only) A conflicting recount is already in progress.
500(blocking=true) Recount failed.
503The 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 /v1/locks
Authorization: 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.

ParameterTypeDefaultDescription
vid / volume-idstringVolume UUID or name (required)
inouint640Inode 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).

StatusMeaning
200Locks deleted; body reports the count.
400Missing/unresolvable volume, or an invalid ino.
401Missing or invalid bearer token.
500Deletion failed.