openapi: 3.0.3 info: title: go-faster/fs admin API description: > Administrative API for a go-faster/fs S3 server: instance info and runtime management of access keys (create, list and delete credentials without a restart). Intended to be served on a separate, authenticated admin listener. version: 0.1.0 servers: - url: "http://localhost:8090" paths: /api/v1/info: get: operationId: "getInfo" summary: "Instance info" description: "Build information, uptime and whether authentication is enabled." responses: 200: description: Instance info. content: "application/json": schema: $ref: "#/components/schemas/InstanceInfo" default: $ref: "#/components/responses/Error" /api/v1/access-keys: get: operationId: "listAccessKeys" summary: "List access keys" description: "Every credential the server accepts, secrets omitted." responses: 200: description: Access keys. content: "application/json": schema: $ref: "#/components/schemas/AccessKeyList" default: $ref: "#/components/responses/Error" post: operationId: "createAccessKey" summary: "Create an access key" description: > Create a runtime credential. The access key and secret are generated when not supplied. The secret is returned only in this response and cannot be retrieved later. requestBody: required: true content: "application/json": schema: $ref: "#/components/schemas/CreateAccessKeyRequest" responses: 201: description: Created credential (includes the secret). content: "application/json": schema: $ref: "#/components/schemas/CreatedAccessKey" default: $ref: "#/components/responses/Error" /api/v1/cluster/rebalance: get: operationId: "getRebalanceStatus" summary: "Cluster rebalance status" description: > State and progress of the cluster rebalance runner on this node, the persisted resume cursor and the depth of the async repair queue. State is "disabled" when the server is not in cluster mode. responses: 200: description: Rebalance status. content: "application/json": schema: $ref: "#/components/schemas/RebalanceStatus" default: $ref: "#/components/responses/Error" post: operationId: "controlRebalance" summary: "Control the cluster rebalance" description: > Start, pause or resume the cluster-wide rebalance from this node. At most one rebalance runs cluster-wide (etcd election); starting on several nodes leaves the extras waiting as standby runners. Pausing stops this node's runner and keeps the resume cursor, so a later start/resume — on any node — continues where it left off. requestBody: required: true content: "application/json": schema: $ref: "#/components/schemas/RebalanceControlRequest" responses: 200: description: Status after applying the action. content: "application/json": schema: $ref: "#/components/schemas/RebalanceStatus" default: $ref: "#/components/responses/Error" /api/v1/cluster/status: get: operationId: "getClusterStatus" summary: "Cluster-wide status" description: > Cluster-wide view read from the control plane: the agreed schema version, every node with its disks and reported capacity, aggregate capacity, placement skew and whether a rebalance is currently running. State is "disabled" when the server is not in cluster mode. responses: 200: description: Cluster status. content: "application/json": schema: $ref: "#/components/schemas/ClusterStatus" default: $ref: "#/components/responses/Error" /api/v1/cluster/migrate: get: operationId: "getMigrationStatus" summary: "Cluster schema migration status" description: > The schema version the cluster has agreed on, the version this binary implements, and the migrations still pending between them. State is "disabled" when the server is not in cluster mode. responses: 200: description: Migration status. content: "application/json": schema: $ref: "#/components/schemas/MigrationStatus" default: $ref: "#/components/responses/Error" post: operationId: "applyMigrations" summary: "Apply pending schema migrations" description: > Apply every pending migration in order, under the cluster-wide migrate election, and record the new schema version — the admin-API equivalent of `fs cluster migrate`. Run it once a rolling upgrade has replaced every node's binary; until then the cluster keeps operating at the old schema. Returns 409 when a migration is already running or when the cluster's schema is newer than this binary implements. responses: 200: description: Status after applying the pending migrations. content: "application/json": schema: $ref: "#/components/schemas/MigrationStatus" default: $ref: "#/components/responses/Error" /api/v1/reload: post: operationId: "reloadConfig" summary: "Reload hot-reloadable configuration" description: > Re-read the configuration file and apply the parts that change without a restart — the config-defined credentials and grants, the anonymously readable buckets and the TLS certificate — exactly as SIGHUP does. Runtime credentials created through this API are preserved. Returns what was reloaded and the config revision now in effect, so an orchestrator can confirm a config change landed without shelling into the process. Returns 501 on a listener with nothing to reload (the headless cluster admin serves no S3 data). responses: 200: description: Reload applied. content: "application/json": schema: $ref: "#/components/schemas/ReloadResult" default: $ref: "#/components/responses/Error" /api/v1/access-keys/{accessKey}: delete: operationId: "deleteAccessKey" summary: "Delete an access key" description: "Remove a runtime credential. Config-defined credentials cannot be deleted." parameters: - name: accessKey in: path required: true description: "The access key ID to delete." schema: type: string responses: 204: description: Deleted. default: $ref: "#/components/responses/Error" /api/v1/buckets/{bucket}/scheme: get: operationId: "getBucketScheme" summary: "Get a bucket's replication scheme" description: > The bucket's effective replication scheme, its explicit override (empty when the bucket follows the cluster default) and the cluster default. Returns 404 when the bucket does not exist and 501 when the server is not in cluster mode (a single-node server has no per-bucket schemes). parameters: - name: bucket in: path required: true description: "The bucket name." schema: type: string responses: 200: description: Bucket scheme. content: "application/json": schema: $ref: "#/components/schemas/BucketScheme" default: $ref: "#/components/responses/Error" put: operationId: "setBucketScheme" summary: "Set or clear a bucket's replication scheme" description: > Override the bucket's replication scheme, or clear the override to restore the cluster default. The scheme must parse ("rf2.5", "rf3" or "ec:k,m") and the current topology must be able to host it. The change affects new writes cluster-wide within seconds; existing objects convert through repair/rebalance. Returns the effective scheme after applying. Returns 400 when the scheme is invalid or the topology cannot host it, 404 when the bucket does not exist and 501 when the server is not in cluster mode. parameters: - name: bucket in: path required: true description: "The bucket name." schema: type: string requestBody: required: true content: "application/json": schema: $ref: "#/components/schemas/SetBucketSchemeRequest" responses: 200: description: Bucket scheme after applying the change. content: "application/json": schema: $ref: "#/components/schemas/BucketScheme" default: $ref: "#/components/responses/Error" /api/v1/public-read-buckets: get: operationId: "getPublicReadBuckets" summary: "List public-read buckets" description: > Buckets readable anonymously (unsigned GET/HEAD/list), cluster-wide. Available only with cluster-wide credentials (auth.source: etcd); returns 501 otherwise, where public-read buckets are managed in the config file per node. responses: 200: description: The public-read bucket list. content: "application/json": schema: $ref: "#/components/schemas/PublicReadBuckets" default: $ref: "#/components/responses/Error" put: operationId: "setPublicReadBuckets" summary: "Replace the public-read bucket list" description: > Replace the cluster-wide public-read bucket list. The change propagates to every node within seconds with no restart. Returns the stored list. Returns 400 on an invalid bucket name and 501 when not using cluster-wide credentials. requestBody: required: true content: "application/json": schema: $ref: "#/components/schemas/SetPublicReadBucketsRequest" responses: 200: description: The public-read bucket list after applying the change. content: "application/json": schema: $ref: "#/components/schemas/PublicReadBuckets" default: $ref: "#/components/responses/Error" components: schemas: InstanceInfo: type: object required: - version - commit - go_version - os - arch - start_time - uptime_seconds - auth_enabled properties: version: type: string example: "v0.1.0" commit: type: string example: "abcdef0" go_version: type: string example: "go1.25.0" os: type: string example: "linux" arch: type: string example: "amd64" start_time: type: string format: date-time uptime_seconds: type: number format: double auth_enabled: type: boolean description: "Whether SigV4 authentication is enabled." config_revision: type: string description: > Opaque marker echoed from the loaded config file's "revision" field, updated on reload. Empty when the config sets none. An orchestrator writes a revision into each node's config and reads it back here to confirm the node has applied that revision. ReloadResult: type: object required: - reloaded properties: reloaded: type: array description: "What the reload applied: any of \"credentials\", \"tls\"." items: type: string config_revision: type: string description: "The config revision in effect after the reload (see InstanceInfo.config_revision)." Permission: type: string description: "Access level; Admin includes Write includes Read." enum: - read - write - admin Grant: type: object required: - bucket - permission properties: bucket: type: string description: "Bucket glob pattern; \"*\" matches all buckets." example: "uploads-*" permission: $ref: "#/components/schemas/Permission" Source: type: string description: "Where the credential is defined." enum: - config - managed AccessKey: type: object required: - access_key - grants - source properties: access_key: type: string example: "AKIAIOSFODNN7EXAMPLE" grants: type: array items: $ref: "#/components/schemas/Grant" source: $ref: "#/components/schemas/Source" created_at: type: string format: date-time description: "Creation time, for runtime-managed credentials." AccessKeyList: type: object required: - keys properties: keys: type: array items: $ref: "#/components/schemas/AccessKey" CreateAccessKeyRequest: type: object required: - grants properties: access_key: type: string description: "Access key ID; generated when omitted." secret_key: type: string description: "Secret access key; generated when omitted." grants: type: array items: $ref: "#/components/schemas/Grant" CreatedAccessKey: type: object required: - access_key - secret_key - grants - created_at properties: access_key: type: string secret_key: type: string description: "Secret access key — shown once, not retrievable later." grants: type: array items: $ref: "#/components/schemas/Grant" created_at: type: string format: date-time RebalanceState: type: string description: > Runner state on this node: "disabled" (not cluster mode), "idle" (never started since boot), "waiting" (campaigning for the cluster-wide slot behind another runner), "running", "paused" (stopped by an operator, resume cursor kept), "done" (last run completed) or "failed" (last run aborted with an error). enum: - disabled - idle - waiting - running - paused - done - failed RebalanceStatus: type: object required: - state - objects - relocated - failed - repair_queue_depth properties: state: $ref: "#/components/schemas/RebalanceState" objects: type: integer description: "Objects processed by the current or last run on this node." relocated: type: integer description: "Objects the run changed (fragments moved, sidecars rewritten, old copies retired)." failed: type: integer description: "Objects whose repair errored; the run continues past them." cursor_bucket: type: string description: "Persisted resume cursor: everything up to and including (bucket, key) is done." cursor_key: type: string started_at: type: string format: date-time description: "When the current or last run started on this node." finished_at: type: string format: date-time description: "When the last run ended on this node." error_message: type: string description: "Why the last run failed, for state \"failed\"." repair_queue_depth: type: integer description: "Pending async replication-remainder tasks on this node (repair backlog indicator)." RebalanceAction: type: string enum: - start - pause - resume RebalanceControlRequest: type: object required: - action properties: action: $ref: "#/components/schemas/RebalanceAction" ClusterDisk: type: object required: - id - weight properties: id: type: string weight: type: number format: double description: "Relative placement weight; not positive means drained (out of placement)." total_bytes: type: integer format: int64 description: "Reported filesystem size; 0 when the node has not reported capacity." free_bytes: type: integer format: int64 fullness: type: number format: double description: "Used fraction (0..1); omitted when capacity is unknown." has_data: type: boolean description: >- Whether the disk still holds any fragment, as the node itself reports it. False means drained: its data has been moved off and the volume can be deleted. Omitted when the node did not report live state, or could not probe the disk (see data_error) — absent means unknown, never drained. Capacity cannot answer this, since it comes from statfs: a disk holding no fragments still reports bytes used. data_error: type: string description: "Why the disk could not be probed; has_data is then absent." fragments: type: integer format: int64 description: >- How many fragments the disk holds, from the node's occupancy index. Present only once that index has been anchored by a scan — absent means "not counted yet", never "empty". This is drain progress, not the drain verdict: has_data is what says the disk is safe to remove. bytes: type: integer format: int64 description: >- What those fragments occupy, payload only — smaller than the used space implied by total_bytes/free_bytes, which come from statfs and include filesystem overhead. Absent under the same conditions as fragments. ClusterNode: type: object required: - id - disks properties: id: type: string addr: type: string description: "Peer address the node advertises." rack: type: string description: "Failure-domain label." disks: type: array items: $ref: "#/components/schemas/ClusterDisk" live: $ref: "#/components/schemas/ClusterNodeLive" live_error: type: string description: > Why live state is missing for this node: unreachable, or running a binary that does not serve it. ClusterNodeLive: type: object description: > A node's live runtime state, fetched from the node itself over the peer transport — what only the running process knows, as opposed to the control-plane state in etcd. required: - repair_queue_depth - rebalance_state - rebalance_objects - rebalance_relocated - rebalance_failed - scrub_passes - scrub_objects - scrub_repaired - scrub_failed - rebuilt_fragments - swept_stale - corrupt_replicas - converted_objects - ec_unverified properties: version: type: string description: "The node's binary version — a half-upgraded cluster shows mixed values." schema_version: type: integer description: "Schema version the node's binary implements." uptime_seconds: type: number format: double repair_queue_depth: type: integer description: "Objects with pending async replication/repair work on the node." rebalance_state: $ref: "#/components/schemas/RebalanceState" rebalance_objects: type: integer rebalance_relocated: type: integer rebalance_failed: type: integer rebalance_error: type: string scrub_passes: type: integer format: int64 scrub_objects: type: integer format: int64 scrub_repaired: type: integer format: int64 scrub_failed: type: integer format: int64 rebuilt_fragments: type: integer format: int64 description: "Fragments rebuilt by scrubs on the node (repair lag indicator)." swept_stale: type: integer format: int64 corrupt_replicas: type: integer format: int64 description: "Replica payloads that failed checksum verification (bit-rot)." converted_objects: type: integer format: int64 description: "Objects rewritten to their bucket's current scheme." ec_unverified: type: boolean description: "The node's last scrub pass saw an EC set failing parity verification." ClusterState: type: string enum: - disabled - ok description: "\"disabled\" when the server is not in cluster mode." ClusterStatus: type: object required: - state - schema_version - binary_schema_version - node_count - disk_count - total_bytes - free_bytes - placement_skew - rebalance_running - repair_queue_depth - nodes_reporting - nodes_not_reporting - nodes properties: state: $ref: "#/components/schemas/ClusterState" schema_version: type: integer description: "Schema version the cluster has agreed on (etcd)." binary_schema_version: type: integer description: "Schema version this binary implements." node_count: type: integer disk_count: type: integer total_bytes: type: integer format: int64 description: "Sum of reported disk capacity across the cluster." free_bytes: type: integer format: int64 placement_skew: type: number format: double description: "Max minus min disk fullness across disks reporting capacity." rebalance_running: type: boolean description: "Whether a rebalance runner currently holds the cluster-wide election." rebalance_cursor_bucket: type: string description: "Resume cursor of an in-progress/last rebalance, if any." rebalance_cursor_key: type: string repair_queue_depth: type: integer description: > Pending async replication/repair work summed over the nodes that reported live state. nodes_reporting: type: integer description: "Nodes that answered the live-state request." nodes_not_reporting: type: integer description: > Nodes that did not answer: unreachable, or running a binary that does not serve live state. nodes: type: array items: $ref: "#/components/schemas/ClusterNode" Migration: type: object description: "A pending schema migration." required: - version - description properties: version: type: integer description: "Schema version this migration produces." description: type: string MigrationStatus: type: object description: "Cluster schema state and the migrations pending between cluster and binary." required: - state - cluster_schema_version - binary_schema_version - up_to_date - running - pending properties: state: $ref: "#/components/schemas/ClusterState" cluster_schema_version: type: integer description: > Schema version the cluster has agreed on; 0 when none is recorded yet (no node has joined). binary_schema_version: type: integer description: "Schema version this binary implements." up_to_date: type: boolean description: "True when nothing is pending: the cluster is at the binary's schema version." running: type: boolean description: "True while this process is applying migrations." pending: type: array items: $ref: "#/components/schemas/Migration" last_applied: type: array description: "Versions applied by the most recent run on this process." items: type: integer last_error: type: string description: "Why the most recent run on this process failed." BucketScheme: type: object description: "A bucket's effective replication scheme and whether it overrides the cluster default." required: - bucket - scheme - cluster_default - is_default properties: bucket: type: string scheme: type: string description: "The effective scheme: the override when set, otherwise the cluster default." example: "ec:4,2" override: type: string description: "The explicit override; empty when the bucket follows the cluster default." cluster_default: type: string description: "The cluster default scheme, applied to buckets without an override." example: "rf2.5" is_default: type: boolean description: "True when the bucket has no override and follows the cluster default." SetBucketSchemeRequest: type: object description: "Set or clear a bucket's replication-scheme override." properties: scheme: type: string description: > The scheme override to apply, e.g. "rf3" or "ec:4,2". An empty string (or "default") clears the override, restoring the cluster default. example: "ec:4,2" PublicReadBuckets: type: object description: "The cluster-wide list of anonymously-readable buckets." required: - buckets properties: buckets: type: array description: "Bucket names readable without credentials." items: type: string SetPublicReadBucketsRequest: type: object description: "Replace the cluster-wide public-read bucket list." required: - buckets properties: buckets: type: array description: "The full new list; an empty array clears all public-read access." items: type: string Error: title: Structured error description: Error occurred while processing request type: object required: - error_message properties: error_message: type: string example: "Something went wrong" responses: Error: description: Structured error response. content: application/json: schema: $ref: "#/components/schemas/Error"