{
  "openapi": "3.0.3",
  "info": {
    "title": "Beezifi Storage API",
    "description": "REST API for Beezifi Object Storage. Authenticate with an API key (`X-API-Key`) or a Bearer JWT obtained from `/api/auth/login`. All authenticated endpoints require one or the other unless noted.",
    "version": "1.0.0",
    "contact": {
      "name": "Beezifi Support",
      "url": "https://storage.beezifi.com/developers"
    }
  },
  "servers": [
    {
      "url": "https://storage.beezifi.com/api",
      "description": "Production"
    }
  ],
  "security": [
    { "ApiKeyAuth": [] },
    { "BearerAuth": [] }
  ],
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Long-lived API key. Generate one via `POST /auth/api-keys` after logging in."
      },
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Short-lived JWT returned by `POST /auth/login`."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "message": { "type": "string", "example": "Unauthorized" }
        },
        "required": ["message"]
      },
      "Organization": {
        "type": "object",
        "properties": {
          "id":               { "type": "string", "format": "uuid" },
          "name":             { "type": "string", "example": "Acme Corp" },
          "slug":             { "type": "string", "example": "acme-corp" },
          "plan_status":      { "type": "string", "enum": ["trial", "active", "expired"], "example": "active" },
          "plan_expires_at":  { "type": "string", "format": "date-time", "nullable": true },
          "storage_tier":     { "type": "string", "example": "S2" },
          "bandwidth_tier":   { "type": "string", "example": "B1" },
          "storage_quota_bytes": { "type": "integer", "example": 107374182400 },
          "storage_used_bytes":  { "type": "integer", "example": 4831838208 },
          "bandwidth_used_bytes": { "type": "integer", "example": 1073741824 }
        }
      },
      "User": {
        "type": "object",
        "properties": {
          "id":            { "type": "string", "format": "uuid" },
          "full_name":     { "type": "string", "example": "Jane Smith" },
          "email":         { "type": "string", "format": "email" },
          "role":          { "type": "string", "enum": ["owner", "admin", "member"], "example": "owner" },
          "totp_enabled":  { "type": "boolean", "example": false },
          "created_at":    { "type": "string", "format": "date-time" }
        }
      },
      "AuthResponse": {
        "type": "object",
        "properties": {
          "token":        { "type": "string", "description": "JWT for use in `Authorization: Bearer` header" },
          "user":         { "$ref": "#/components/schemas/User" },
          "organization": { "$ref": "#/components/schemas/Organization" }
        }
      },
      "ApiKey": {
        "type": "object",
        "properties": {
          "id":         { "type": "string", "format": "uuid" },
          "label":      { "type": "string", "example": "CI deploy key" },
          "key_prefix": { "type": "string", "example": "bz_k1_abc1" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "ApiKeyCreated": {
        "allOf": [
          { "$ref": "#/components/schemas/ApiKey" },
          {
            "type": "object",
            "properties": {
              "key": {
                "type": "string",
                "description": "Full API key — shown once, store it immediately.",
                "example": "bz_k1_abc1xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
              }
            }
          }
        ]
      },
      "Bucket": {
        "type": "object",
        "properties": {
          "id":                  { "type": "string", "format": "uuid" },
          "name":                { "type": "string", "example": "my-assets" },
          "visibility":          { "type": "string", "enum": ["private", "public"], "example": "private" },
          "versioning_enabled":  { "type": "boolean", "example": true },
          "region":              { "type": "string", "example": "local" },
          "storage_quota_bytes": { "type": "integer", "nullable": true, "example": null },
          "object_count":        { "type": "integer", "example": 142 },
          "total_size_bytes":    { "type": "integer", "example": 5368709120 },
          "created_at":          { "type": "string", "format": "date-time" }
        }
      },
      "PolicyStatement": {
        "type": "object",
        "required": ["effect", "action", "resourceType", "resourceId", "subjectType", "subjectId"],
        "properties": {
          "effect":       { "type": "string", "enum": ["allow", "deny"] },
          "action":       { "type": "string", "example": "object.read" },
          "resourceType": { "type": "string", "enum": ["bucket", "object"] },
          "resourceId":   { "type": "string", "example": "*" },
          "subjectType":  { "type": "string", "enum": ["user", "role", "api_key"] },
          "subjectId":    { "type": "string", "example": "user-uuid-here" },
          "conditions":   { "type": "object", "additionalProperties": true, "nullable": true }
        }
      },
      "Object": {
        "type": "object",
        "properties": {
          "id":            { "type": "string", "format": "uuid" },
          "bucket_id":     { "type": "string", "format": "uuid" },
          "object_key":    { "type": "string", "example": "images/avatar.png" },
          "content_type":  { "type": "string", "example": "image/png" },
          "size_bytes":    { "type": "integer", "example": 204800 },
          "etag":          { "type": "string", "example": "d41d8cd98f00b204e9800998ecf8427e" },
          "is_deleted":    { "type": "boolean", "example": false },
          "created_at":    { "type": "string", "format": "date-time" },
          "updated_at":    { "type": "string", "format": "date-time" }
        }
      },
      "SignedUrl": {
        "type": "object",
        "properties": {
          "url":       { "type": "string", "format": "uri", "example": "https://storage.beezifi.com/api/public/objects/bucket-id/images/avatar.png?signature=...&expires=1748000000&method=GET" },
          "expiresAt": { "type": "string", "format": "date-time" },
          "method":    { "type": "string", "enum": ["GET", "PUT"] }
        }
      },
      "MultipartSession": {
        "type": "object",
        "properties": {
          "uploadId":        { "type": "string", "format": "uuid" },
          "objectKey":       { "type": "string", "example": "videos/intro.mp4" },
          "bucketId":        { "type": "string", "format": "uuid" },
          "totalSizeBytes":  { "type": "integer", "nullable": true },
          "status":          { "type": "string", "enum": ["pending", "completed", "failed"] },
          "created_at":      { "type": "string", "format": "date-time" }
        }
      },
      "Member": {
        "type": "object",
        "properties": {
          "id":         { "type": "string", "format": "uuid" },
          "full_name":  { "type": "string", "example": "Alice Johnson" },
          "email":      { "type": "string", "format": "email" },
          "role":       { "type": "string", "enum": ["owner", "admin", "member"] },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "AuditLog": {
        "type": "object",
        "properties": {
          "id":             { "type": "string", "format": "uuid" },
          "action":         { "type": "string", "example": "object.uploaded" },
          "resource_type":  { "type": "string", "example": "object" },
          "resource_id":    { "type": "string" },
          "actor_user_id":  { "type": "string", "format": "uuid", "nullable": true },
          "ip_address":     { "type": "string", "example": "203.0.113.42" },
          "payload":        { "type": "object", "additionalProperties": true },
          "created_at":     { "type": "string", "format": "date-time" }
        }
      },
      "UsageSummary": {
        "type": "object",
        "properties": {
          "storage_used_bytes":   { "type": "integer", "example": 4831838208 },
          "storage_quota_bytes":  { "type": "integer", "example": 107374182400 },
          "bandwidth_used_bytes": { "type": "integer", "example": 1073741824 },
          "object_count":         { "type": "integer", "example": 1042 },
          "bucket_count":         { "type": "integer", "example": 4 }
        }
      },
      "Plan": {
        "type": "object",
        "properties": {
          "plan_status":      { "type": "string", "enum": ["trial", "active", "expired"] },
          "plan_expires_at":  { "type": "string", "format": "date-time", "nullable": true },
          "storage_tier":     { "type": "string", "nullable": true, "example": "S2" },
          "bandwidth_tier":   { "type": "string", "nullable": true, "example": "B1" },
          "storage_quota_bytes": { "type": "integer", "nullable": true },
          "bandwidth_quota_bytes": { "type": "integer", "nullable": true }
        }
      },
      "Invoice": {
        "type": "object",
        "properties": {
          "id":          { "type": "string", "format": "uuid" },
          "source":      { "type": "string", "enum": ["payment", "promo"], "example": "payment" },
          "description": { "type": "string", "example": "S2 storage + B1 bandwidth — 12 months" },
          "amount":      { "type": "integer", "description": "Amount in cents (USD)", "example": 9900 },
          "currency":    { "type": "string", "example": "usd" },
          "status":      { "type": "string", "example": "succeeded" },
          "date":        { "type": "integer", "description": "Unix timestamp", "example": 1716134400 }
        }
      },
      "TotpStatus": {
        "type": "object",
        "properties": {
          "enabled": { "type": "boolean" }
        }
      },
      "TotpSetup": {
        "type": "object",
        "properties": {
          "secret":      { "type": "string", "description": "Base32 TOTP secret" },
          "otpauth_url": { "type": "string", "description": "otpauth:// URI for QR code generation" }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid credentials",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "message": "Unauthorized" }
          }
        }
      },
      "Forbidden": {
        "description": "Authenticated but insufficient permissions",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "message": "Forbidden" }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "message": "Not found" }
          }
        }
      },
      "ValidationError": {
        "description": "Request body failed validation",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "message": "name: String must contain at least 3 character(s)" }
          }
        }
      }
    },
    "parameters": {
      "bucketId": {
        "name": "bucketId",
        "in": "path",
        "required": true,
        "schema": { "type": "string", "format": "uuid" },
        "description": "Bucket UUID"
      },
      "objectKey": {
        "name": "objectKey",
        "in": "path",
        "required": true,
        "schema": { "type": "string" },
        "description": "Object key (path). May contain slashes.",
        "example": "images/avatar.png"
      },
      "userId": {
        "name": "userId",
        "in": "path",
        "required": true,
        "schema": { "type": "string", "format": "uuid" },
        "description": "User UUID"
      }
    }
  },
  "tags": [
    { "name": "Auth",     "description": "Register, log in, and manage API keys" },
    { "name": "Buckets",  "description": "Create and configure storage buckets" },
    { "name": "Objects",  "description": "Upload, download, delete, and recover objects" },
    { "name": "Members",  "description": "Manage organization members (owner only)" },
    { "name": "Settings", "description": "Password and two-factor authentication" },
    { "name": "Usage",    "description": "Storage usage, audit logs, and upload history" },
    { "name": "Billing",  "description": "Plan info, purchases, and promo codes" }
  ],
  "paths": {
    "/auth/register": {
      "post": {
        "tags": ["Auth"],
        "summary": "Register a new organization and owner account",
        "operationId": "register",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["organizationName", "organizationSlug", "fullName", "email", "password"],
                "properties": {
                  "organizationName": { "type": "string", "minLength": 3, "maxLength": 255, "example": "Acme Corp" },
                  "organizationSlug": { "type": "string", "minLength": 3, "maxLength": 255, "pattern": "^[a-z0-9-]+$", "example": "acme-corp" },
                  "fullName":         { "type": "string", "minLength": 2, "maxLength": 255, "example": "Jane Smith" },
                  "email":            { "type": "string", "format": "email", "example": "jane@acme.com" },
                  "password":         { "type": "string", "minLength": 8, "maxLength": 128, "example": "s3cur3P@ss!" }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Account created — returns JWT and user/org details",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AuthResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "409": {
            "description": "Email or slug already in use",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "message": "Email already registered" }
              }
            }
          }
        }
      }
    },
    "/auth/login": {
      "post": {
        "tags": ["Auth"],
        "summary": "Log in and receive a JWT",
        "operationId": "login",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["email", "password"],
                "properties": {
                  "email":     { "type": "string", "format": "email", "example": "jane@acme.com" },
                  "password":  { "type": "string", "minLength": 8, "example": "s3cur3P@ss!" },
                  "totpToken": { "type": "string", "description": "6-digit TOTP code — required if 2FA is enabled", "example": "123456" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful login",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AuthResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/auth/forgot-password": {
      "post": {
        "tags": ["Auth"],
        "summary": "Request a password reset email",
        "operationId": "forgotPassword",
        "description": "Sends a reset link to the given email address if it belongs to an active account. Always returns 200 to avoid revealing whether an address is registered.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["email"],
                "properties": {
                  "email": { "type": "string", "format": "email", "example": "jane@acme.com" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Reset email queued (or address not found — response is identical either way)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": { "type": "string", "example": "If that address is registered you will receive a reset email shortly." }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/ValidationError" }
        }
      }
    },
    "/auth/reset-password": {
      "post": {
        "tags": ["Auth"],
        "summary": "Set a new password using a reset token",
        "operationId": "resetPassword",
        "description": "Validates the one-time token from the reset email, updates the user's password, and disables TOTP if it was enabled. The token is valid for 1 hour and can only be used once.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["token", "newPassword"],
                "properties": {
                  "token":       { "type": "string", "description": "Raw token from the reset URL query parameter" },
                  "newPassword": { "type": "string", "minLength": 8, "example": "NewP@ss456!" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Password updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": { "type": "string", "example": "Password updated. You can now sign in with your new password." }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid, expired, or already-used token",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "message": "This reset link has expired" }
              }
            }
          }
        }
      }
    },
    "/auth/api-keys": {
      "get": {
        "tags": ["Auth"],
        "summary": "List API keys for the organization",
        "operationId": "listApiKeys",
        "responses": {
          "200": {
            "description": "Array of API keys (full key value is never returned after creation)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": { "$ref": "#/components/schemas/ApiKey" }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "post": {
        "tags": ["Auth"],
        "summary": "Create a new API key",
        "operationId": "createApiKey",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": { "type": "string", "example": "CI deploy key" }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "API key created — the `key` field is shown only once",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiKeyCreated" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/buckets": {
      "get": {
        "tags": ["Buckets"],
        "summary": "List all buckets in the organization",
        "operationId": "listBuckets",
        "responses": {
          "200": {
            "description": "Array of buckets",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": { "$ref": "#/components/schemas/Bucket" }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "post": {
        "tags": ["Buckets"],
        "summary": "Create a new bucket",
        "operationId": "createBucket",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["name"],
                "properties": {
                  "name":               { "type": "string", "minLength": 3, "maxLength": 255, "pattern": "^[a-z0-9.-]+$", "example": "my-assets" },
                  "visibility":         { "type": "string", "enum": ["private", "public"], "default": "private" },
                  "versioningEnabled":  { "type": "boolean", "default": true },
                  "region":             { "type": "string", "minLength": 2, "maxLength": 64, "default": "local" },
                  "storageQuotaBytes":  { "type": "integer", "minimum": 1, "nullable": true, "example": 10737418240 }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Bucket created",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Bucket" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "409": {
            "description": "A bucket with this name already exists in the organization",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/buckets/{bucketId}/policy": {
      "get": {
        "tags": ["Buckets"],
        "summary": "Get the access policy for a bucket",
        "operationId": "getBucketPolicy",
        "parameters": [{ "$ref": "#/components/parameters/bucketId" }],
        "responses": {
          "200": {
            "description": "Array of policy statements",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": { "$ref": "#/components/schemas/PolicyStatement" }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "put": {
        "tags": ["Buckets"],
        "summary": "Replace the access policy for a bucket",
        "operationId": "setBucketPolicy",
        "parameters": [{ "$ref": "#/components/parameters/bucketId" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["statements"],
                "properties": {
                  "statements": {
                    "type": "array",
                    "items": { "$ref": "#/components/schemas/PolicyStatement" }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated policy statements",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": { "$ref": "#/components/schemas/PolicyStatement" }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/buckets/{bucketId}/signed-url": {
      "post": {
        "tags": ["Buckets"],
        "summary": "Generate a time-limited signed URL for an object",
        "operationId": "generateSignedUrl",
        "parameters": [{ "$ref": "#/components/parameters/bucketId" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["objectKey"],
                "properties": {
                  "objectKey":        { "type": "string", "minLength": 1, "maxLength": 1024, "example": "images/avatar.png" },
                  "expiresInSeconds": { "type": "integer", "minimum": 60, "maximum": 86400, "default": 3600, "example": 3600 },
                  "method":           { "type": "string", "enum": ["GET", "PUT"], "default": "GET" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed URL",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SignedUrl" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/buckets/{bucketId}/objects": {
      "get": {
        "tags": ["Objects"],
        "summary": "List objects in a bucket",
        "operationId": "listObjects",
        "parameters": [
          { "$ref": "#/components/parameters/bucketId" },
          {
            "name": "prefix",
            "in": "query",
            "schema": { "type": "string" },
            "description": "Filter by key prefix (simulates folder browsing)",
            "example": "images/"
          },
          {
            "name": "search",
            "in": "query",
            "schema": { "type": "string" },
            "description": "Full-text search across object keys",
            "example": "avatar"
          }
        ],
        "responses": {
          "200": {
            "description": "Array of objects",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": { "$ref": "#/components/schemas/Object" }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/buckets/{bucketId}/objects/upload": {
      "post": {
        "tags": ["Objects"],
        "summary": "Upload an object",
        "operationId": "uploadObject",
        "description": "Multipart form upload. Use the `file` field for the binary payload and `objectKey` to control the destination path. Include a JSON string in `metadata` for custom key/value pairs.",
        "parameters": [{ "$ref": "#/components/parameters/bucketId" }],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["file"],
                "properties": {
                  "file":      { "type": "string", "format": "binary", "description": "File to upload" },
                  "objectKey": { "type": "string", "example": "images/avatar.png", "description": "Destination key. Defaults to the original filename." },
                  "metadata":  { "type": "string", "description": "JSON-encoded object of custom metadata", "example": "{\"alt\":\"User avatar\"}" }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Object stored",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Object" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/buckets/{bucketId}/multipart/initiate": {
      "post": {
        "tags": ["Objects"],
        "summary": "Initiate a multipart upload session",
        "operationId": "initiateMultipartUpload",
        "description": "Start a tracked multipart upload for large files. Returns an `uploadId` to reference while uploading parts.",
        "parameters": [{ "$ref": "#/components/parameters/bucketId" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["objectKey"],
                "properties": {
                  "objectKey":      { "type": "string", "minLength": 1, "maxLength": 1024, "example": "videos/intro.mp4" },
                  "totalSizeBytes": { "type": "integer", "minimum": 1, "nullable": true, "example": 1073741824 }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Multipart session initiated",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/MultipartSession" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/buckets/{bucketId}/objects/{objectKey}": {
      "get": {
        "tags": ["Objects"],
        "summary": "Download an object",
        "operationId": "downloadObject",
        "description": "Streams the raw file bytes. The response `Content-Type` and `Content-Disposition` headers are set from the stored object metadata.",
        "parameters": [
          { "$ref": "#/components/parameters/bucketId" },
          { "$ref": "#/components/parameters/objectKey" }
        ],
        "responses": {
          "200": {
            "description": "File bytes",
            "content": {
              "application/octet-stream": {
                "schema": { "type": "string", "format": "binary" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "delete": {
        "tags": ["Objects"],
        "summary": "Soft-delete an object",
        "operationId": "deleteObject",
        "description": "Marks the object as deleted. It can be recovered via `POST /buckets/{bucketId}/objects/{objectKey}/recover`.",
        "parameters": [
          { "$ref": "#/components/parameters/bucketId" },
          { "$ref": "#/components/parameters/objectKey" }
        ],
        "responses": {
          "200": {
            "description": "Object deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": { "type": "boolean", "example": true },
                    "object":  { "$ref": "#/components/schemas/Object" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/buckets/{bucketId}/objects/{objectKey}/recover": {
      "post": {
        "tags": ["Objects"],
        "summary": "Recover a soft-deleted object",
        "operationId": "recoverObject",
        "parameters": [
          { "$ref": "#/components/parameters/bucketId" },
          { "$ref": "#/components/parameters/objectKey" }
        ],
        "responses": {
          "200": {
            "description": "Object recovered",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "recovered": { "type": "boolean", "example": true },
                    "object":    { "$ref": "#/components/schemas/Object" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/public/objects/{bucketId}/{objectKey}": {
      "get": {
        "tags": ["Objects"],
        "summary": "Download a public or signed-URL object (no auth required)",
        "operationId": "publicDownload",
        "description": "Serves an object without authentication. Works for buckets with `visibility: public`, or for any bucket when a valid signed URL is provided via query parameters.",
        "security": [],
        "parameters": [
          {
            "name": "bucketId",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" }
          },
          {
            "name": "objectKey",
            "in": "path",
            "required": true,
            "schema": { "type": "string" },
            "example": "images/avatar.png"
          },
          {
            "name": "signature",
            "in": "query",
            "schema": { "type": "string" },
            "description": "HMAC signature from a generated signed URL"
          },
          {
            "name": "expires",
            "in": "query",
            "schema": { "type": "integer" },
            "description": "Unix expiry timestamp from the signed URL"
          },
          {
            "name": "method",
            "in": "query",
            "schema": { "type": "string", "enum": ["GET", "PUT"] },
            "description": "HTTP method the signed URL was generated for"
          }
        ],
        "responses": {
          "200": {
            "description": "File bytes",
            "content": {
              "application/octet-stream": {
                "schema": { "type": "string", "format": "binary" }
              }
            }
          },
          "403": {
            "description": "Bucket is private and no valid signed URL was supplied",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "message": "Bucket is private and requires a signed URL" }
              }
            }
          },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/members": {
      "get": {
        "tags": ["Members"],
        "summary": "List organization members",
        "operationId": "listMembers",
        "description": "Owner-only.",
        "responses": {
          "200": {
            "description": "Array of members",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": { "$ref": "#/components/schemas/Member" }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      },
      "post": {
        "tags": ["Members"],
        "summary": "Add a member to the organization",
        "operationId": "addMember",
        "description": "Owner-only. Creates a new user account and associates it with the organization.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["fullName", "email", "password"],
                "properties": {
                  "fullName": { "type": "string", "example": "Bob Lee" },
                  "email":    { "type": "string", "format": "email", "example": "bob@acme.com" },
                  "password": { "type": "string", "minLength": 8, "example": "s3cur3P@ss!" },
                  "role":     { "type": "string", "enum": ["admin", "member"], "default": "member" }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Member added",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Member" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/members/{userId}": {
      "patch": {
        "tags": ["Members"],
        "summary": "Update a member's role",
        "operationId": "updateMemberRole",
        "description": "Owner-only.",
        "parameters": [{ "$ref": "#/components/parameters/userId" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["role"],
                "properties": {
                  "role": { "type": "string", "enum": ["admin", "member"] }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Role updated",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Member" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "delete": {
        "tags": ["Members"],
        "summary": "Remove a member from the organization",
        "operationId": "removeMember",
        "description": "Owner-only. Cannot remove the owner themselves.",
        "parameters": [{ "$ref": "#/components/parameters/userId" }],
        "responses": {
          "200": {
            "description": "Member removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "removed": { "type": "boolean", "example": true }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/settings/password": {
      "patch": {
        "tags": ["Settings"],
        "summary": "Change the authenticated user's password",
        "operationId": "changePassword",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["currentPassword", "newPassword"],
                "properties": {
                  "currentPassword": { "type": "string", "example": "OldP@ss123" },
                  "newPassword":     { "type": "string", "minLength": 8, "example": "NewP@ss456!" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Password changed",
            "content": {
              "application/json": {
                "schema": { "type": "object", "properties": { "ok": { "type": "boolean", "example": true } } }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/settings/totp": {
      "get": {
        "tags": ["Settings"],
        "summary": "Get TOTP (2FA) status for the current user",
        "operationId": "totpStatus",
        "responses": {
          "200": {
            "description": "TOTP status",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/TotpStatus" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "delete": {
        "tags": ["Settings"],
        "summary": "Disable TOTP (2FA)",
        "operationId": "totpDisable",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["token"],
                "properties": {
                  "token": { "type": "string", "description": "Current 6-digit TOTP code to confirm disable", "example": "123456" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "TOTP disabled",
            "content": {
              "application/json": {
                "schema": { "type": "object", "properties": { "ok": { "type": "boolean", "example": true } } }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/settings/totp/setup": {
      "post": {
        "tags": ["Settings"],
        "summary": "Begin TOTP setup — generates a secret and QR code URI",
        "operationId": "totpSetup",
        "responses": {
          "200": {
            "description": "TOTP setup info",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/TotpSetup" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/settings/totp/verify": {
      "post": {
        "tags": ["Settings"],
        "summary": "Verify TOTP token and enable 2FA",
        "operationId": "totpVerify",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["token"],
                "properties": {
                  "token": { "type": "string", "description": "6-digit TOTP code from authenticator app", "example": "123456" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "2FA enabled",
            "content": {
              "application/json": {
                "schema": { "type": "object", "properties": { "ok": { "type": "boolean", "example": true } } }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/usage/summary": {
      "get": {
        "tags": ["Usage"],
        "summary": "Get storage and bandwidth usage summary",
        "operationId": "usageSummary",
        "responses": {
          "200": {
            "description": "Usage totals for the organization",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/UsageSummary" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/audit-logs": {
      "get": {
        "tags": ["Usage"],
        "summary": "List audit log entries",
        "operationId": "auditLogs",
        "parameters": [
          {
            "name": "objectKey",
            "in": "query",
            "schema": { "type": "string" },
            "description": "Filter by object key substring"
          },
          {
            "name": "action",
            "in": "query",
            "schema": { "type": "string" },
            "description": "Filter by action (e.g. `object.uploaded`, `object.downloaded`)"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": { "type": "integer", "minimum": 0, "default": 0 }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated audit log entries",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "total":  { "type": "integer" },
                    "rows":   { "type": "array", "items": { "$ref": "#/components/schemas/AuditLog" } }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/uploads": {
      "get": {
        "tags": ["Usage"],
        "summary": "List multipart upload sessions",
        "operationId": "listUploads",
        "responses": {
          "200": {
            "description": "Array of multipart upload sessions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": { "$ref": "#/components/schemas/MultipartSession" }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/billing/plan": {
      "get": {
        "tags": ["Billing"],
        "summary": "Get current plan details",
        "operationId": "getPlan",
        "responses": {
          "200": {
            "description": "Plan info",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Plan" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/billing/purchase": {
      "post": {
        "tags": ["Billing"],
        "summary": "Create a Stripe payment intent for a plan purchase",
        "operationId": "purchase",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["storageTier", "bandwidthTier", "termMonths"],
                "properties": {
                  "storageTier":   { "type": "string", "example": "S2" },
                  "bandwidthTier": { "type": "string", "example": "B1" },
                  "termMonths":    { "type": "integer", "minimum": 1, "example": 12 }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Stripe PaymentIntent client secret",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "clientSecret": { "type": "string" },
                    "amount":       { "type": "integer", "description": "Amount in cents" }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/ValidationError" },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/billing/charges": {
      "get": {
        "tags": ["Billing"],
        "summary": "List invoice history (payments and promo redemptions)",
        "operationId": "getCharges",
        "responses": {
          "200": {
            "description": "Array of invoices sorted newest-first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": { "$ref": "#/components/schemas/Invoice" }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/billing/redeem-promo": {
      "post": {
        "tags": ["Billing"],
        "summary": "Redeem a promo code",
        "operationId": "redeemPromo",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["code"],
                "properties": {
                  "code": { "type": "string", "example": "BEEZ-FREE-3MO" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Promo redeemed — plan extended",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message":         { "type": "string", "example": "Promo redeemed! Your plan has been extended by 3 months." },
                    "months_added":    { "type": "integer", "example": 3 },
                    "plan_expires_at": { "type": "string", "format": "date-time" }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid, expired, or already-used promo code",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "message": "Promo code already redeemed" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    }
  }
}
