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 }