Files

1446 lines
49 KiB
YAML

openapi: 3.1.0
info:
title: Google Drive S3 API
version: 1.0.0
description: |
An intentionally small S3-compatible object-storage API backed by Google Drive.
Authenticate every non-public request with AWS Signature Version 4. Browser clients
should receive short-lived presigned URLs from a backend-for-frontend, never credentials.
servers:
- url: https://{worker-host}
variables:
worker-host:
default: your-worker.example.workers.dev
security:
- sigv4: []
tags:
- name: Objects
- name: Buckets
- name: Multipart uploads
- name: Dashboard Auth
- name: Dashboard Status
paths:
/auth/login:
post:
tags: [Dashboard Auth]
operationId: authLogin
summary: Login to dashboard
description: Authenticate with SHA-256 hashed dashboard password.
security: []
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/LoginRequest' }
responses:
'200':
description: Login successful.
content:
application/json:
schema: { $ref: '#/components/schemas/LoginResponse' }
'400':
description: Invalid request.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
'401':
description: Invalid password.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
'429':
description: Too many failed attempts.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
'503':
description: Dashboard authentication not configured.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
/auth/session:
get:
tags: [Dashboard Auth]
operationId: authSession
summary: Verify session token
security:
- bearerAuth: []
responses:
'200':
description: Session is valid.
content:
application/json:
schema:
type: object
properties:
valid: { type: boolean, example: true }
'401':
description: Invalid or expired session.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
'503':
description: Dashboard authentication not configured.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
/auth/logout:
post:
tags: [Dashboard Auth]
operationId: authLogout
summary: Logout of dashboard session
security:
- bearerAuth: []
responses:
'204':
description: Successfully logged out.
'503':
description: Dashboard authentication not configured.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
/api/status:
get:
tags: [Dashboard Status]
operationId: getGatewayStatus
summary: Get gateway and storage health status
security:
- bearerAuth: []
responses:
'200':
description: Gateway status and Drive account/quota overview.
content:
application/json:
schema: { $ref: '#/components/schemas/GatewayStatus' }
'401':
description: Invalid or expired session.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
'503':
description: Dashboard authentication not configured.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
/api/integration:
get:
tags: [Dashboard Status]
operationId: getIntegrationInfo
summary: Get S3 connection details and access keys
description: Everything an external S3 client needs — endpoint, region, path-style flag, bucket list and the current access keys (metadata only, no secrets).
security:
- bearerAuth: []
responses:
'200':
description: Integration info with access key metadata.
content:
application/json:
schema: { $ref: '#/components/schemas/IntegrationInfo' }
'401':
description: Invalid or expired session.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
'503':
description: Storage root folder or authentication not configured.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
/api/integration/keys:
post:
tags: [Dashboard Status]
operationId: createAccessKey
summary: Create a named S3 access key pair
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/CreateAccessKeyRequest' }
responses:
'201':
description: Key pair created. The secret is only returned here.
content:
application/json:
schema: { $ref: '#/components/schemas/S3AccessKeyFull' }
'400':
description: Invalid label or the maximum of 5 live keys was reached.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
'401':
description: Invalid or expired session.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
/api/integration/keys/{id}/rotate:
post:
tags: [Dashboard Status]
operationId: rotateAccessKey
summary: Rotate an access key with an optional grace period
description: Creates a replacement key carrying the same label. The old key is deleted immediately when graceSeconds is 0, otherwise it keeps authenticating until the grace period elapses. Changes propagate within 60 seconds due to edge caching.
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/RotateAccessKeyRequest' }
responses:
'200':
description: Rotation result with the new full pair and the old key's retirement timestamp.
content:
application/json:
schema: { $ref: '#/components/schemas/RotateAccessKeyResponse' }
'400':
description: Invalid graceSeconds value.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
'401':
description: Invalid or expired session.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
'404':
description: Access key not found.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
/api/integration/keys/{id}/secret:
get:
tags: [Dashboard Status]
operationId: revealAccessKeySecret
summary: Reveal an access key secret
description: Rate-limited to 20 reveals per IP per 60 seconds.
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
'200':
description: The secret access key.
content:
application/json:
schema: { $ref: '#/components/schemas/AccessKeySecret' }
'401':
description: Invalid or expired session.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
'404':
description: Access key not found.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
'429':
description: Too many reveal attempts.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
/api/integration/keys/{id}:
delete:
tags: [Dashboard Status]
operationId: revokeAccessKey
summary: Revoke an access key immediately
description: Deleting the last remaining key locks out all S3 clients until a new key is created. Propagates within 60 seconds.
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
'204':
description: Access key revoked.
'401':
description: Invalid or expired session.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
'404':
description: Access key not found.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
/api/buckets:
get:
tags: [Dashboard Status]
operationId: getBucketStats
summary: Get bucket list and object metrics
security:
- bearerAuth: []
parameters:
- name: refresh
in: query
description: Set to 1 to bypass KV cache.
required: false
schema: { type: string, example: "1" }
responses:
'200':
description: Bucket statistics and aggregate totals.
content:
application/json:
schema: { $ref: '#/components/schemas/BucketStats' }
'401':
description: Invalid or expired session.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
'503':
description: Storage root folder or authentication not configured.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
post:
tags: [Dashboard Status]
operationId: createBucket
summary: Create a new bucket
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/CreateBucketRequest' }
responses:
'201':
description: Bucket created.
content:
application/json:
schema: { $ref: '#/components/schemas/BucketRecord' }
'400':
description: Invalid bucket name.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
'401':
description: Invalid or expired session.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
'409':
description: Bucket already exists.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
'503':
description: Storage root folder or authentication not configured.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
/api/buckets/{name}:
parameters:
- name: name
in: path
required: true
schema: { type: string }
patch:
tags: [Dashboard Status]
operationId: updateBucket
summary: Update bucket settings (toggle public read or rename)
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/UpdateBucketRequest' }
responses:
'200':
description: Bucket updated.
content:
application/json:
schema: { $ref: '#/components/schemas/BucketRecord' }
'400':
description: Invalid request.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
'401':
description: Invalid or expired session.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
'404':
description: Bucket not found.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
'409':
description: Target bucket name conflict.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
delete:
tags: [Dashboard Status]
operationId: deleteBucket
summary: Delete an empty bucket
security:
- bearerAuth: []
responses:
'204':
description: Bucket deleted.
'401':
description: Invalid or expired session.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
'404':
description: Bucket not found.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
'409':
description: Bucket is not empty.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
/api/import-candidates:
get:
tags: [Dashboard Status]
operationId: listImportCandidates
summary: List Drive folders available for import
security:
- bearerAuth: []
responses:
'200':
description: List of folders under Drive root.
content:
application/json:
schema: { $ref: '#/components/schemas/ImportCandidatesResponse' }
'401':
description: Invalid or expired session.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
/api/import:
post:
tags: [Dashboard Status]
operationId: importBuckets
summary: Import folders from Drive root into storage root folder
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/ImportBucketsRequest' }
responses:
'200':
description: Import results.
content:
application/json:
schema: { $ref: '#/components/schemas/ImportResult' }
'400':
description: Invalid body.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
'401':
description: Invalid or expired session.
content:
application/json:
schema: { $ref: '#/components/schemas/AuthError' }
/api/objects:
get:
tags: [Objects API]
operationId: listObjectsApi
summary: List objects and folders in a bucket (Dashboard Session Auth)
security:
- sessionBearer: []
parameters:
- in: query
name: bucket
required: true
schema: { type: string }
- in: query
name: prefix
schema: { type: string, default: "" }
- in: query
name: delimiter
schema: { type: string, default: "/" }
responses:
'200':
description: Object and folder listing.
content:
application/json:
schema:
type: object
properties:
bucket: { type: string }
prefix: { type: string }
delimiter: { type: string, nullable: true }
folders:
type: array
items:
type: object
properties:
prefix: { type: string }
name: { type: string }
objects:
type: array
items:
type: object
properties:
key: { type: string }
name: { type: string }
size: { type: integer }
contentType: { type: string }
lastModified: { type: string, nullable: true }
etag: { type: string }
truncated: { type: boolean }
'400':
description: Missing query parameters.
'401':
description: Unauthorized.
'404':
description: Bucket not found.
delete:
tags: [Objects API]
operationId: deleteObjectApi
summary: Delete an object (Dashboard Session Auth)
security:
- sessionBearer: []
parameters:
- in: query
name: bucket
required: true
schema: { type: string }
- in: query
name: key
required: true
schema: { type: string }
responses:
'204':
description: Object deleted.
'401':
description: Unauthorized.
'404':
description: Bucket or object not found.
/api/objects/metadata:
get:
tags: [Objects API]
operationId: getObjectMetadataApi
summary: Get object metadata (Dashboard Session Auth)
security:
- sessionBearer: []
parameters:
- in: query
name: bucket
required: true
schema: { type: string }
- in: query
name: key
required: true
schema: { type: string }
responses:
'200':
description: Object metadata.
content:
application/json:
schema:
type: object
properties:
key: { type: string }
name: { type: string }
size: { type: integer }
contentType: { type: string }
lastModified: { type: string, nullable: true }
etag: { type: string }
'404':
description: Object not found.
/api/objects/content:
get:
tags: [Objects API]
operationId: getObjectContentApi
summary: Download object content (Dashboard Session Auth or Ticket)
parameters:
- in: query
name: bucket
required: true
schema: { type: string }
- in: query
name: key
required: true
schema: { type: string }
- in: query
name: ticket
schema: { type: string }
responses:
'200':
description: Binary object data.
'206':
description: Partial content.
'404':
description: Object not found.
put:
tags: [Objects API]
operationId: putObjectContentApi
summary: Direct single-part upload (Dashboard Session Auth)
security:
- sessionBearer: []
parameters:
- in: query
name: bucket
required: true
schema: { type: string }
- in: query
name: key
required: true
schema: { type: string }
responses:
'200':
description: Object uploaded.
content:
application/json:
schema:
type: object
properties:
key: { type: string }
etag: { type: string }
size: { type: integer }
/api/objects/folder:
post:
tags: [Objects API]
operationId: createFolderApi
summary: Create a folder (Dashboard Session Auth)
security:
- sessionBearer: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [bucket, prefix]
properties:
bucket: { type: string }
prefix: { type: string }
responses:
'201':
description: Folder created.
'409':
description: Folder already exists.
delete:
tags: [Objects API]
operationId: deleteFolderApi
summary: Delete a folder (Dashboard Session Auth)
security:
- sessionBearer: []
parameters:
- in: query
name: bucket
required: true
schema: { type: string }
- in: query
name: prefix
required: true
schema: { type: string }
- in: query
name: recursive
schema: { type: string, enum: ["0", "1"] }
responses:
'204':
description: Folder deleted.
'409':
description: Folder is not empty.
/api/objects/download-ticket:
post:
tags: [Objects API]
operationId: createDownloadTicket
summary: Create a temporary single-use download ticket (Dashboard Session Auth)
security:
- sessionBearer: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [bucket, key]
properties:
bucket: { type: string }
key: { type: string }
responses:
'201':
description: Download ticket created.
content:
application/json:
schema:
type: object
properties:
ticket: { type: string }
downloadUrl: { type: string }
expiresIn: { type: integer }
/api/objects/uploads:
post:
tags: [Objects API]
operationId: initiateMultipartApi
summary: Initiate a multipart upload (Dashboard Session Auth)
security:
- sessionBearer: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [bucket, key]
properties:
bucket: { type: string }
key: { type: string }
contentType: { type: string }
responses:
'201':
description: Upload initiated.
content:
application/json:
schema:
type: object
properties:
uploadId: { type: string }
bucket: { type: string }
key: { type: string }
partSize: { type: integer }
delete:
tags: [Objects API]
operationId: abortMultipartApi
summary: Abort a multipart upload (Dashboard Session Auth)
security:
- sessionBearer: []
parameters:
- in: query
name: bucket
required: true
schema: { type: string }
- in: query
name: key
required: true
schema: { type: string }
- in: query
name: uploadId
required: true
schema: { type: string }
responses:
'204':
description: Upload aborted.
/api/objects/uploads/part:
put:
tags: [Objects API]
operationId: uploadPartApi
summary: Upload a part (Dashboard Session Auth)
security:
- sessionBearer: []
parameters:
- in: query
name: bucket
required: true
schema: { type: string }
- in: query
name: key
required: true
schema: { type: string }
- in: query
name: uploadId
required: true
schema: { type: string }
- in: query
name: partNumber
required: true
schema: { type: integer }
responses:
'200':
description: Part uploaded.
content:
application/json:
schema:
type: object
properties:
partNumber: { type: integer }
etag: { type: string }
/api/objects/uploads/complete:
post:
tags: [Objects API]
operationId: completeMultipartApi
summary: Complete a multipart upload (Dashboard Session Auth)
security:
- sessionBearer: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [bucket, key, uploadId, parts]
properties:
bucket: { type: string }
key: { type: string }
uploadId: { type: string }
parts:
type: array
items:
type: object
required: [partNumber, etag]
properties:
partNumber: { type: integer }
etag: { type: string }
totalSize: { type: integer }
responses:
'200':
description: Upload completed.
content:
application/json:
schema:
type: object
properties:
key: { type: string }
etag: { type: string }
/api/objects/uploads/parts:
get:
tags: [Objects API]
operationId: listPartsApi
summary: List uploaded parts (Dashboard Session Auth)
security:
- sessionBearer: []
parameters:
- in: query
name: bucket
required: true
schema: { type: string }
- in: query
name: key
required: true
schema: { type: string }
- in: query
name: uploadId
required: true
schema: { type: string }
responses:
'200':
description: Parts list.
/{bucket}:
parameters:
- $ref: '#/components/parameters/Bucket'
get:
tags: [Buckets]
operationId: listObjects
summary: List objects in a bucket
description: List objects recursively, or use `delimiter=/` to return immediate folders as `CommonPrefixes`.
parameters:
- $ref: '#/components/parameters/Prefix'
- $ref: '#/components/parameters/Delimiter'
responses:
'200':
description: Object listing.
content:
application/xml:
schema: { $ref: '#/components/schemas/ListBucketResult' }
'403': { $ref: '#/components/responses/AccessDenied' }
'405': { $ref: '#/components/responses/MethodNotAllowed' }
head:
tags: [Buckets]
operationId: headBucket
summary: Check bucket access
responses:
'200': { description: The bucket is configured and accessible. }
'403': { $ref: '#/components/responses/AccessDenied' }
'405': { $ref: '#/components/responses/MethodNotAllowed' }
/{bucket}?uploads:
parameters:
- $ref: '#/components/parameters/Bucket'
get:
tags: [Multipart uploads]
operationId: listMultipartUploads
summary: List multipart uploads
description: Always returns an empty list; active multipart uploads cannot be enumerated.
responses:
'200':
description: Empty multipart upload listing.
content:
application/xml:
schema: { $ref: '#/components/schemas/ListMultipartUploadsResult' }
'403': { $ref: '#/components/responses/AccessDenied' }
/{bucket}/{key}:
parameters:
- $ref: '#/components/parameters/Bucket'
- $ref: '#/components/parameters/Key'
put:
tags: [Objects]
operationId: putObject
summary: Create or replace an object
parameters:
- $ref: '#/components/parameters/ContentType'
requestBody:
required: true
content:
application/octet-stream:
schema: { type: string, format: binary }
'*/*':
schema: { type: string, format: binary }
responses:
'200':
description: Object uploaded.
headers:
ETag: { $ref: '#/components/headers/ETag' }
'403': { $ref: '#/components/responses/AccessDenied' }
'501': { $ref: '#/components/responses/NotImplemented' }
get:
tags: [Objects]
operationId: getObject
summary: Download an object
parameters:
- name: Range
in: header
schema: { type: string, example: bytes=0-1023 }
responses:
'200':
description: Full object data.
headers:
ETag: { $ref: '#/components/headers/ETag' }
Content-Length: { $ref: '#/components/headers/ContentLength' }
Last-Modified: { $ref: '#/components/headers/LastModified' }
Accept-Ranges: { schema: { type: string, example: bytes } }
content:
application/octet-stream:
schema: { type: string, format: binary }
'206':
description: Requested byte range.
headers:
Content-Range: { schema: { type: string, example: bytes 0-1023/4096 } }
Content-Length: { $ref: '#/components/headers/ContentLength' }
content:
application/octet-stream:
schema: { type: string, format: binary }
'403': { $ref: '#/components/responses/AccessDenied' }
'404': { $ref: '#/components/responses/NoSuchKey' }
head:
tags: [Objects]
operationId: headObject
summary: Get object metadata
responses:
'200':
description: Object metadata in headers.
headers:
ETag: { $ref: '#/components/headers/ETag' }
Content-Length: { $ref: '#/components/headers/ContentLength' }
Last-Modified: { $ref: '#/components/headers/LastModified' }
Accept-Ranges: { schema: { type: string, example: bytes } }
'403': { $ref: '#/components/responses/AccessDenied' }
'404': { $ref: '#/components/responses/NoSuchKey' }
delete:
tags: [Objects]
operationId: deleteObject
summary: Delete an object
responses:
'204': { description: Object deleted. }
'403': { $ref: '#/components/responses/AccessDenied' }
'404': { $ref: '#/components/responses/NoSuchKey' }
/{bucket}/{key}?uploads:
parameters:
- $ref: '#/components/parameters/Bucket'
- $ref: '#/components/parameters/Key'
post:
tags: [Multipart uploads]
operationId: createMultipartUpload
summary: Initiate a multipart upload
parameters:
- $ref: '#/components/parameters/ContentType'
responses:
'200':
description: Upload initiated.
content:
application/xml:
schema: { $ref: '#/components/schemas/InitiateMultipartUploadResult' }
'403': { $ref: '#/components/responses/AccessDenied' }
'501': { $ref: '#/components/responses/NotImplemented' }
/{bucket}/{key}?uploadId={uploadId}&partNumber={partNumber}:
parameters:
- $ref: '#/components/parameters/Bucket'
- $ref: '#/components/parameters/Key'
- $ref: '#/components/parameters/UploadId'
- $ref: '#/components/parameters/PartNumber'
put:
tags: [Multipart uploads]
operationId: uploadPart
summary: Upload one multipart part
description: Parts must arrive strictly sequentially beginning with part number 1.
requestBody:
required: true
content:
application/octet-stream:
schema: { type: string, format: binary }
responses:
'200':
description: Part accepted.
headers:
ETag: { $ref: '#/components/headers/ETag' }
'400': { $ref: '#/components/responses/InvalidArgument' }
'403': { $ref: '#/components/responses/AccessDenied' }
'404': { $ref: '#/components/responses/NoSuchUpload' }
'503': { $ref: '#/components/responses/SlowDown' }
/{bucket}/{key}?uploadId={uploadId}:
parameters:
- $ref: '#/components/parameters/Bucket'
- $ref: '#/components/parameters/Key'
- $ref: '#/components/parameters/UploadId'
post:
tags: [Multipart uploads]
operationId: completeMultipartUpload
summary: Complete a multipart upload
requestBody:
required: true
content:
application/xml:
schema: { $ref: '#/components/schemas/CompleteMultipartUpload' }
responses:
'200':
description: Upload completed.
content:
application/xml:
schema: { $ref: '#/components/schemas/CompleteMultipartUploadResult' }
'400': { $ref: '#/components/responses/InvalidArgument' }
'403': { $ref: '#/components/responses/AccessDenied' }
'404': { $ref: '#/components/responses/NoSuchUpload' }
get:
tags: [Multipart uploads]
operationId: listParts
summary: List uploaded parts
parameters:
- name: part-number-marker
in: query
schema: { type: integer, minimum: 0, default: 0 }
- name: max-parts
in: query
schema: { type: integer, minimum: 1, maximum: 1000, default: 1000 }
responses:
'200':
description: Current part list.
content:
application/xml:
schema: { $ref: '#/components/schemas/ListPartsResult' }
'400': { $ref: '#/components/responses/InvalidArgument' }
'403': { $ref: '#/components/responses/AccessDenied' }
'404': { $ref: '#/components/responses/NoSuchUpload' }
delete:
tags: [Multipart uploads]
operationId: abortMultipartUpload
summary: Abort a multipart upload
responses:
'204': { description: Upload aborted. }
'403': { $ref: '#/components/responses/AccessDenied' }
'404': { $ref: '#/components/responses/NoSuchUpload' }
components:
securitySchemes:
sigv4:
type: apiKey
in: header
name: Authorization
description: AWS Signature Version 4 header authentication or equivalent `X-Amz-*` presigned query parameters.
bearerAuth:
type: http
scheme: bearer
description: Session token returned from `/auth/login`.
parameters:
Bucket:
name: bucket
in: path
required: true
schema: { type: string }
Key:
name: key
in: path
required: true
schema: { type: string }
Prefix:
name: prefix
in: query
schema: { type: string, default: '' }
Delimiter:
name: delimiter
in: query
schema: { type: string, example: / }
UploadId:
name: uploadId
in: query
required: true
schema: { type: string }
PartNumber:
name: partNumber
in: query
required: true
schema: { type: integer, minimum: 1, maximum: 10000 }
ContentType:
name: Content-Type
in: header
schema: { type: string, example: application/octet-stream }
headers:
ETag:
schema: { type: string, example: '"d41d8cd98f00b204e9800998ecf8427e"' }
ContentLength:
schema: { type: integer, minimum: 0 }
LastModified:
schema: { type: string, format: date-time }
responses:
AccessDenied:
description: Authentication failed, the access key is not recognized, the presigned URL expired, or bucket access is denied.
content: { application/xml: { schema: { $ref: '#/components/schemas/S3Error' } } }
NoSuchKey:
description: The object does not exist.
content: { application/xml: { schema: { $ref: '#/components/schemas/S3Error' } } }
NoSuchUpload:
description: The multipart upload does not exist.
content: { application/xml: { schema: { $ref: '#/components/schemas/S3Error' } } }
InvalidArgument:
description: A required parameter or body is invalid.
content: { application/xml: { schema: { $ref: '#/components/schemas/S3Error' } } }
NotImplemented:
description: This S3 operation is not supported.
content: { application/xml: { schema: { $ref: '#/components/schemas/S3Error' } } }
SlowDown:
description: Parts are out of sequence or the upload is busy; retry later.
content: { application/xml: { schema: { $ref: '#/components/schemas/S3Error' } } }
MethodNotAllowed:
description: The method/path combination is unsupported.
content: { application/xml: { schema: { $ref: '#/components/schemas/S3Error' } } }
schemas:
LoginRequest:
type: object
required: [passwordHash]
properties:
passwordHash:
type: string
description: SHA-256 hash of the dashboard password in lowercase hex.
example: ef92b778bafe771e89245b89ecbc08a44a4e166c06659911881f383d4473e94f
LoginResponse:
type: object
required: [token, expiresIn]
properties:
token:
type: string
description: Session token.
expiresIn:
type: integer
description: Token TTL in seconds.
example: 43200
GatewayStatus:
type: object
required: [gateway, drive, checkedAt]
properties:
gateway:
type: object
required: [status, region, multipartEnabled, etagStyle, docsEnabled, buckets, publicReadBuckets, rootFolder, corsOrigins, credentials]
properties:
status: { type: string, enum: [ok, degraded] }
region: { type: string, example: auto }
multipartEnabled: { type: boolean }
etagStyle: { type: string, enum: [md5, multipart] }
docsEnabled: { type: boolean }
buckets: { type: array, items: { type: string } }
publicReadBuckets: { type: array, items: { type: string } }
rootFolder:
type: object
required: [name, id, configured]
properties:
name: { type: string, nullable: true }
id: { type: string, nullable: true }
configured: { type: boolean }
corsOrigins: { type: array, items: { type: string } }
credentials:
type: object
required: [s3Keys, googleOAuth, dashboardPassword]
properties:
s3Keys: { type: boolean }
googleOAuth: { type: boolean }
dashboardPassword: { type: boolean }
drive:
type: object
required: [connected, account, quota, error]
properties:
connected: { type: boolean }
account:
type: object
nullable: true
properties:
email: { type: string, nullable: true }
displayName: { type: string, nullable: true }
quota:
type: object
nullable: true
properties:
limit: { type: integer, nullable: true }
usage: { type: integer }
usageInDrive: { type: integer }
usageInDriveTrash: { type: integer }
free: { type: integer, nullable: true }
percentUsed: { type: number, nullable: true }
error: { type: string, nullable: true }
checkedAt:
type: string
format: date-time
BucketStats:
type: object
required: [buckets, totals, cachedAt]
properties:
buckets:
type: array
items:
type: object
required: [name, objectCount, totalSize, lastModified, truncated, publicRead, error]
properties:
name: { type: string }
objectCount: { type: integer }
totalSize: { type: integer }
lastModified: { type: string, format: date-time, nullable: true }
truncated: { type: boolean }
publicRead: { type: boolean }
error: { type: string, nullable: true }
totals:
type: object
required: [buckets, objectCount, totalSize]
properties:
buckets: { type: integer }
objectCount: { type: integer }
totalSize: { type: integer }
cachedAt:
type: string
format: date-time
BucketRecord:
type: object
required: [name, folderId, publicRead, createdTime]
properties:
name: { type: string, example: assets }
folderId: { type: string, example: 1A2b3C4d5E6f }
publicRead: { type: boolean, example: false }
createdTime: { type: string, format: date-time, nullable: true }
CreateBucketRequest:
type: object
required: [name]
properties:
name: { type: string, example: assets }
publicRead: { type: boolean, default: false }
UpdateBucketRequest:
type: object
properties:
publicRead: { type: boolean }
name: { type: string }
ImportCandidate:
type: object
required: [name, folderId, objectCount]
properties:
name: { type: string, example: legacy-photos }
folderId: { type: string, example: 1A2b3C4d5E6f }
objectCount: { type: integer, example: 42 }
ImportCandidatesResponse:
type: object
required: [candidates]
properties:
candidates:
type: array
items: { $ref: '#/components/schemas/ImportCandidate' }
ImportBucketsRequest:
type: object
required: [names]
properties:
names:
type: array
items: { type: string }
ImportResult:
type: object
required: [imported, failed]
properties:
imported:
type: array
items: { type: string }
failed:
type: array
items:
type: object
required: [name, error]
properties:
name: { type: string }
error: { type: string }
AuthError:
type: object
required: [message]
properties:
message:
type: string
example: Invalid password
S3Error:
type: object
xml: { name: Error }
required: [Code, Message, RequestId]
properties:
Code: { type: string, xml: { name: Code } }
Message: { type: string, xml: { name: Message } }
Resource: { type: string, xml: { name: Resource } }
RequestId: { type: string, xml: { name: RequestId } }
Object:
type: object
xml: { name: Contents }
properties:
Key: { type: string }
LastModified: { type: string, format: date-time }
ETag: { type: string }
Size: { type: integer }
StorageClass: { type: string, example: STANDARD }
ListBucketResult:
type: object
xml: { name: ListBucketResult, namespace: 'http://s3.amazonaws.com/doc/2006-03-01/' }
properties:
Name: { type: string }
Prefix: { type: string }
Delimiter: { type: string }
MaxKeys: { type: integer, example: 1000 }
IsTruncated: { type: boolean }
Contents: { type: array, items: { $ref: '#/components/schemas/Object' }, xml: { wrapped: false } }
InitiateMultipartUploadResult:
type: object
xml: { name: InitiateMultipartUploadResult, namespace: 'http://s3.amazonaws.com/doc/2006-03-01/' }
required: [Bucket, Key, UploadId]
properties:
Bucket: { type: string }
Key: { type: string }
UploadId: { type: string }
CompleteMultipartUpload:
type: object
xml: { name: CompleteMultipartUpload }
required: [Part]
properties:
Part:
type: array
minItems: 1
xml: { wrapped: false }
items:
type: object
xml: { name: Part }
required: [PartNumber, ETag]
properties:
PartNumber: { type: integer }
ETag: { type: string }
CompleteMultipartUploadResult:
type: object
xml: { name: CompleteMultipartUploadResult, namespace: 'http://s3.amazonaws.com/doc/2006-03-01/' }
properties:
Location: { type: string }
Bucket: { type: string }
Key: { type: string }
ETag: { type: string }
ListPartsResult:
type: object
xml: { name: ListPartsResult, namespace: 'http://s3.amazonaws.com/doc/2006-03-01/' }
properties:
Bucket: { type: string }
Key: { type: string }
UploadId: { type: string }
NextPartNumberMarker: { type: integer }
IsTruncated: { type: boolean }
Part:
type: array
xml: { wrapped: false }
items:
type: object
properties:
PartNumber: { type: integer }
LastModified: { type: string, format: date-time }
ETag: { type: string }
Size: { type: integer }
ListMultipartUploadsResult:
type: object
xml: { name: ListMultipartUploadsResult, namespace: 'http://s3.amazonaws.com/doc/2006-03-01/' }
properties:
Bucket: { type: string }
KeyMarker: { type: string }
UploadIdMarker: { type: string }
NextKeyMarker: { type: string }
NextUploadIdMarker: { type: string }
MaxUploads: { type: integer, example: 1000 }
IsTruncated: { type: boolean, example: false }
S3AccessKeyMetadata:
type: object
required: [accessKeyId, label, createdAt, expiresAt]
properties:
accessKeyId: { type: string, example: GDS7Q2JXK4M9VBTZRWEA }
label: { type: string, example: rclone-backup }
createdAt: { type: string, format: date-time }
expiresAt: { type: string, format: date-time, nullable: true, description: Set when the key is retiring after a rotation. }
S3AccessKeyFull:
type: object
required: [accessKeyId, secretAccessKey, label, createdAt, expiresAt]
properties:
accessKeyId: { type: string, example: GDS7Q2JXK4M9VBTZRWEA }
secretAccessKey: { type: string, description: Only returned on creation and reveal. }
label: { type: string, example: rclone-backup }
createdAt: { type: string, format: date-time }
expiresAt: { type: string, format: date-time, nullable: true }
CreateAccessKeyRequest:
type: object
required: [label]
properties:
label:
type: string
minLength: 1
maxLength: 32
pattern: '^[a-zA-Z0-9 _-]+$'
example: n8n-media-uploads
RotateAccessKeyRequest:
type: object
required: [graceSeconds]
properties:
graceSeconds:
type: integer
enum: [0, 3600, 86400, 604800]
description: 0 revokes the old key immediately; otherwise the old key keeps working for this long.
default: 86400
RotateAccessKeyResponse:
type: object
required: [created, previous]
properties:
created: { $ref: '#/components/schemas/S3AccessKeyFull' }
previous:
type: object
required: [accessKeyId, expiresAt]
properties:
accessKeyId: { type: string }
expiresAt: { type: string, format: date-time, nullable: true }
AccessKeySecret:
type: object
required: [secretAccessKey]
properties:
secretAccessKey: { type: string }
IntegrationInfo:
type: object
required: [endpoint, region, forcePathStyle, buckets, publicReadBuckets, multipartEnabled, etagStyle, corsOrigins, docsUrl, openApiUrl, accessKeys, limits]
properties:
endpoint: { type: string, format: uri, example: https://s3-google-drive.example.workers.dev }
region: { type: string, example: auto }
forcePathStyle: { type: boolean, const: true }
buckets: { type: array, items: { type: string } }
publicReadBuckets: { type: array, items: { type: string } }
multipartEnabled: { type: boolean }
etagStyle: { type: string, enum: [md5, multipart] }
corsOrigins: { type: array, items: { type: string } }
docsUrl: { type: string, nullable: true }
openApiUrl: { type: string, nullable: true }
accessKeys:
type: array
items: { $ref: '#/components/schemas/S3AccessKeyMetadata' }
limits:
type: object
required: [maxAccessKeys, keyPropagationSeconds, presignExpiryMaxSeconds]
properties:
maxAccessKeys: { type: integer, example: 5 }
keyPropagationSeconds: { type: integer, example: 60, description: Edge-cache TTL — revoke/rotate take up to this long to propagate. }
presignExpiryMaxSeconds: { type: integer, example: 604800 }