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/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. 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/cluster/layout: get: operationId: "getLayout" summary: "Current cluster layout" description: > The layout this node has adopted: version, partitions, the widths it spreads for, each member's role and share of slots, and how many slots a single zone or rack holds per width. Returns 501 when cluster mode is off and 404 before any layout has been applied. responses: 200: description: Layout summary. content: "application/json": schema: $ref: "#/components/schemas/Layout" default: $ref: "#/components/responses/Error" post: operationId: "applyLayout" summary: "Apply a cluster layout" description: > Compute the next layout from the full set of member roles and adopt it on this node; gossip carries it to every other node. Slots keep their node wherever it is still valid, so only the data that has to move does. With dry_run the computed layout and the number of slots that would move are returned without adopting anything. Returns 400 when the roles cannot produce a layout, e.g. fewer members with capacity than the widest width. parameters: - name: dry_run in: query schema: type: boolean default: false requestBody: required: true content: "application/json": schema: $ref: "#/components/schemas/ApplyLayoutRequest" responses: 200: description: The computed layout and what it changes. content: "application/json": schema: $ref: "#/components/schemas/LayoutChange" default: $ref: "#/components/responses/Error" /api/v1/cluster/nodes: get: operationId: "listClusterNodes" summary: "Cluster nodes" description: > This node and every peer it gossips with: address, the layout version each last reported, and whether the last exchange succeeded. Returns 501 when cluster mode is off. responses: 200: description: Nodes. content: "application/json": schema: $ref: "#/components/schemas/ClusterNodeList" 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 LayoutRole: type: object required: - id - capacity properties: id: type: string example: "node-1" zone: type: string example: "dc1" rack: type: string example: "r1" capacity: type: integer format: uint64 description: "Bytes of data the node takes. Zero makes it a gateway that holds no slots." LayoutMember: allOf: - $ref: "#/components/schemas/LayoutRole" - type: object required: - slots properties: slots: type: integer description: "Slots this member holds." LayoutSpread: type: object required: - width - max_per_zone - max_per_rack properties: width: type: integer max_per_zone: type: integer description: "Most slots of one partition's first width slots in a single zone." max_per_rack: type: integer description: "Most slots of one partition's first width slots in a single rack." Layout: type: object required: - version - partitions - widths - members - spread properties: version: type: integer format: uint64 partitions: type: integer widths: type: array items: type: integer members: type: array items: $ref: "#/components/schemas/LayoutMember" spread: type: array items: $ref: "#/components/schemas/LayoutSpread" ApplyLayoutRequest: type: object required: - members properties: members: type: array items: $ref: "#/components/schemas/LayoutRole" widths: type: array description: "Slot prefixes to spread for: 3 for replicated data, k+m per erasure scheme. Default: the current layout's, else [3]." items: type: integer partitions: type: integer description: "Partition count, a power of two. Only used for the first layout; default 256." LayoutChange: type: object required: - layout - moved_slots - applied properties: layout: $ref: "#/components/schemas/Layout" moved_slots: type: integer description: "Slots whose node differs from the current layout." applied: type: boolean ClusterNode: type: object required: - addr - self - up properties: id: type: string addr: type: string self: type: boolean up: type: boolean description: "The last exchange with this peer succeeded. Always true for this node." layout_version: type: integer format: uint64 last_seen: type: string format: date-time error: type: string ClusterNodeList: type: object required: - nodes properties: nodes: type: array items: $ref: "#/components/schemas/ClusterNode" 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"