{
  "openapi": "3.1.0",
  "info": {
    "title": "file-hub API",
    "version": "1.0.0",
    "description": "Microservizio centralizzato per la gestione file multi-progetto con storage PostgreSQL chunked. Upload, download, resize, dedup, ricerca per context/tag, signed URL."
  },
  "servers": [
    {
      "url": "https://api.filehub.foreachsoftware.it",
      "description": "Current environment"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "API Key",
        "description": "Inserisci solo il valore della API key. Swagger aggiunge automaticamente il prefisso Bearer."
      }
    },
    "schemas": {
      "BucketProjectRef": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "color": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "slug",
          "color"
        ],
        "description": "Project relation. Null if the bucket has no project."
      },
      "Bucket": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Bucket ID (cuid)"
          },
          "slug": {
            "type": "string",
            "description": "URL-safe bucket identifier"
          },
          "name": {
            "type": "string",
            "description": "Display name"
          },
          "project": {
            "$ref": "#/components/schemas/BucketProjectRef"
          },
          "projectId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Convenience FK — equal to project?.id ?? null."
          },
          "projectName": {
            "type": [
              "string",
              "null"
            ],
            "description": "DEPRECATED legacy free-form label. Mirrors project.name when linked."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional description"
          },
          "defaultVisibility": {
            "type": "string",
            "enum": [
              "PUBLIC",
              "PRIVATE"
            ],
            "description": "Default file visibility"
          },
          "allowedMimeTypes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Allowed MIME types"
          },
          "maxFileSizeMb": {
            "type": [
              "number",
              "null"
            ],
            "description": "Max file size in MB"
          },
          "totalSizeBytes": {
            "type": "number"
          },
          "fileCount": {
            "type": "integer"
          },
          "ownerId": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "slug",
          "name",
          "project",
          "projectId",
          "projectName",
          "description",
          "defaultVisibility",
          "allowedMimeTypes",
          "maxFileSizeMb",
          "totalSizeBytes",
          "fileCount",
          "ownerId",
          "createdAt",
          "updatedAt"
        ]
      },
      "PaginationMeta": {
        "type": "object",
        "properties": {
          "page": {
            "type": "number"
          },
          "pageSize": {
            "type": "number"
          },
          "total": {
            "type": "number"
          },
          "totalPages": {
            "type": "number"
          }
        },
        "required": [
          "page",
          "pageSize",
          "total",
          "totalPages"
        ]
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "enum": [
              false
            ]
          },
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "details": {}
            },
            "required": [
              "code",
              "message"
            ]
          }
        },
        "required": [
          "success",
          "error"
        ]
      },
      "Project": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Project ID (cuid)"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "color": {
            "type": [
              "string",
              "null"
            ],
            "description": "Hex color (#RRGGBB) for UI tinting"
          },
          "ownerId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Tenant identifier"
          },
          "bucketCount": {
            "type": "integer",
            "minimum": 0
          },
          "totalFiles": {
            "type": "integer",
            "minimum": 0
          },
          "totalSizeBytes": {
            "type": "number",
            "minimum": 0
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "name",
          "slug",
          "description",
          "color",
          "ownerId",
          "bucketCount",
          "totalFiles",
          "totalSizeBytes",
          "createdAt",
          "updatedAt"
        ]
      },
      "File": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "File ID (cuid)"
          },
          "filename": {
            "type": "string"
          },
          "mimeType": {
            "type": "string"
          },
          "sizeBytes": {
            "type": "number"
          },
          "checksum": {
            "type": "string"
          },
          "visibility": {
            "type": "string",
            "enum": [
              "PUBLIC",
              "PRIVATE"
            ]
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "width": {
            "type": [
              "number",
              "null"
            ]
          },
          "height": {
            "type": [
              "number",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "filename",
          "mimeType",
          "sizeBytes",
          "checksum",
          "visibility",
          "tags",
          "category",
          "width",
          "height",
          "createdAt",
          "updatedAt"
        ]
      },
      "ApiKey": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "prefix": {
            "type": "string"
          },
          "permissions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "allowedBuckets": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "rateLimit": {
            "type": "number"
          },
          "isActive": {
            "type": "boolean"
          },
          "lastUsedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "name",
          "prefix",
          "permissions",
          "allowedBuckets",
          "rateLimit",
          "isActive",
          "lastUsedAt",
          "expiresAt",
          "createdAt"
        ]
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Webhook ID (cuid)"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Subscribed event types"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "isActive": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "url",
          "events",
          "description",
          "isActive",
          "createdAt",
          "updatedAt"
        ]
      },
      "WebhookDelivery": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "event": {
            "type": "string"
          },
          "statusCode": {
            "type": [
              "number",
              "null"
            ]
          },
          "attempts": {
            "type": "number"
          },
          "maxAttempts": {
            "type": "number"
          },
          "success": {
            "type": "boolean"
          },
          "lastError": {
            "type": [
              "string",
              "null"
            ]
          },
          "completedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "event",
          "statusCode",
          "attempts",
          "maxAttempts",
          "success",
          "lastError",
          "completedAt",
          "createdAt"
        ]
      }
    },
    "parameters": {}
  },
  "paths": {
    "/health/live": {
      "get": {
        "tags": [
          "Health"
        ],
        "summary": "Liveness check",
        "responses": {
          "200": {
            "description": "Service is alive"
          }
        }
      }
    },
    "/health/ready": {
      "get": {
        "tags": [
          "Health"
        ],
        "summary": "Readiness check",
        "responses": {
          "200": {
            "description": "Service is ready"
          },
          "503": {
            "description": "Not ready"
          }
        }
      }
    },
    "/api/v1/buckets": {
      "get": {
        "tags": [
          "Buckets"
        ],
        "summary": "List buckets with pagination",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": [
                "number",
                "null"
              ],
              "default": 1,
              "description": "Page number"
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "number",
                "null"
              ],
              "default": 20,
              "description": "Items per page"
            },
            "required": false,
            "name": "pageSize",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Search by name/slug"
            },
            "required": false,
            "name": "search",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc",
              "description": "Sort order"
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter by project id"
            },
            "required": false,
            "name": "projectId",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "DEPRECATED — filter by legacy project string. Pass 'null' for unlinked buckets."
            },
            "required": false,
            "name": "projectFilter",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated bucket list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Bucket"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginationMeta"
                    }
                  },
                  "required": [
                    "success",
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Buckets"
        ],
        "summary": "Create a new bucket",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "slug": {
                    "type": "string",
                    "description": "Unique slug",
                    "example": "products"
                  },
                  "name": {
                    "type": "string",
                    "description": "Display name",
                    "example": "Product Images"
                  },
                  "description": {
                    "type": "string"
                  },
                  "projectId": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Optional Project link. Must reference a project in the caller's tenant."
                  },
                  "project": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "DEPRECATED — legacy free-form project label. Auto-promoted to a Project row when projectId is omitted."
                  },
                  "defaultVisibility": {
                    "type": "string",
                    "enum": [
                      "PUBLIC",
                      "PRIVATE"
                    ],
                    "default": "PRIVATE"
                  },
                  "allowedMimeTypes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "example": [
                      "image/*",
                      "application/pdf"
                    ]
                  },
                  "maxFileSizeMb": {
                    "type": "number",
                    "default": 25
                  }
                },
                "required": [
                  "slug",
                  "name",
                  "allowedMimeTypes"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Bucket created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "data": {
                      "$ref": "#/components/schemas/Bucket"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error"
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/api/v1/buckets/{id}": {
      "get": {
        "tags": [
          "Buckets"
        ],
        "summary": "Get bucket by ID or slug",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Bucket ID or slug"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Bucket details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "data": {
                      "$ref": "#/components/schemas/Bucket"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/buckets/{id}/categories": {
      "get": {
        "tags": [
          "Buckets"
        ],
        "summary": "List file categories within a bucket",
        "description": "Aggregates distinct `category` values for non-deleted files in the bucket. `category: null` represents uncategorized files. Sorted by count desc.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Bucket ID or slug"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Categories with file count and total size"
          },
          "404": {
            "description": "Bucket not found"
          }
        }
      },
      "post": {
        "tags": [
          "Buckets"
        ],
        "summary": "Bulk rename / merge a category in a bucket",
        "description": "Updates `category` on every file in the bucket where `category === from`. Pass `null` to target uncategorized files or to clear the category. `from` and `to` must differ.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "from": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "to": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "required": [
                  "from",
                  "to"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Number of files updated"
          },
          "400": {
            "description": "Invalid body / from === to"
          },
          "404": {
            "description": "Bucket not found"
          }
        }
      }
    },
    "/api/v1/projects": {
      "get": {
        "tags": [
          "Projects"
        ],
        "summary": "List projects in the caller's tenant",
        "description": "Paginated list of Project entities scoped to the caller's `ownerId`. Each entry includes bucket count, total file count and total size aggregated server-side via groupBy.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": [
                "number",
                "null"
              ],
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "number",
                "null"
              ],
              "default": 20
            },
            "required": false,
            "name": "pageSize",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "search",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated project list with stats",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Project"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginationMeta"
                    }
                  },
                  "required": [
                    "success",
                    "data",
                    "meta"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Projects"
        ],
        "summary": "Create a new project",
        "description": "Creates a Project under the caller's tenant. Slug is auto-generated from `name` when omitted; collisions are resolved with `-2`, `-3` … suffixes. Requires `admin` permission.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "example": "Acme Corp"
                  },
                  "slug": {
                    "type": "string",
                    "description": "URL-safe identifier. Auto-generated if omitted.",
                    "example": "acme-corp"
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "color": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Hex color (#RRGGBB)",
                    "example": "#14b8a6"
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Project created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "data": {
                      "$ref": "#/components/schemas/Project"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error"
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Insufficient permissions (admin required)"
          }
        }
      }
    },
    "/api/v1/projects/{id}": {
      "get": {
        "tags": [
          "Projects"
        ],
        "summary": "Get a project by id or slug",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Project ID or slug"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Project details with stats",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "data": {
                      "$ref": "#/components/schemas/Project"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "patch": {
        "tags": [
          "Projects"
        ],
        "summary": "Update a project",
        "description": "Patch name / slug / description / color. When `name` changes, the legacy `Bucket.project` string is mirrored on every linked bucket inside the same transaction.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "slug": {
                    "type": "string"
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "color": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Project updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "data": {
                      "$ref": "#/components/schemas/Project"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "delete": {
        "tags": [
          "Projects"
        ],
        "summary": "Delete a project",
        "description": "Removes the project. Linked buckets are preserved (`projectId` becomes null) and their legacy `Bucket.project` string is cleared in the same transaction.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Project deleted; returns count of unlinked buckets"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/files": {
      "get": {
        "tags": [
          "Files"
        ],
        "summary": "List files with filtering and pagination",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": [
                "number",
                "null"
              ],
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "number",
                "null"
              ],
              "default": 20
            },
            "required": false,
            "name": "pageSize",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Filter by bucket slug"
            },
            "required": false,
            "name": "bucketSlug",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "PUBLIC",
                "PRIVATE"
              ]
            },
            "required": false,
            "name": "visibility",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "mimeType",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "search",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Comma-separated tags"
            },
            "required": false,
            "name": "tags",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated file list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/File"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginationMeta"
                    }
                  },
                  "required": [
                    "success",
                    "data",
                    "meta"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/files/{id}": {
      "get": {
        "tags": [
          "Files"
        ],
        "summary": "Get file details",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "File details with variants"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/files/{id}/download": {
      "get": {
        "tags": [
          "Files"
        ],
        "summary": "Download file as binary",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "description": "Variant type: THUMBNAIL, SMALL, MEDIUM, LARGE"
            },
            "required": false,
            "name": "variant",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "File binary content"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/files/{id}/convert-to-webp": {
      "post": {
        "tags": [
          "Files"
        ],
        "summary": "Convert image file to WebP in place",
        "description": "Converts an existing image file to WebP, preserving the file id. Replaces chunks, mime, filename, sizeBytes, checksum, dimensions and regenerates variants. Returns the updated file plus a `conversion` block with previous and new sizes. Already-WebP files return success with `conversion.alreadyWebp: true` and no other side effects.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "File converted (or already WebP)"
          },
          "400": {
            "description": "Not an image / unsupported format"
          },
          "403": {
            "description": "Bucket not allowed for this key"
          },
          "404": {
            "description": "File not found"
          },
          "409": {
            "description": "Checksum collision in bucket"
          }
        }
      }
    },
    "/api/v1/files/{id}/serve": {
      "get": {
        "tags": [
          "Files"
        ],
        "summary": "Serve file (public or signed URL)",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "description": "Signed URL expiry timestamp"
            },
            "required": false,
            "name": "expires",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "HMAC signature"
            },
            "required": false,
            "name": "sig",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "File served inline"
          },
          "403": {
            "description": "Access denied"
          }
        }
      }
    },
    "/api/v1/files/search": {
      "get": {
        "tags": [
          "Files"
        ],
        "summary": "Full-text search files",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "Search query"
            },
            "required": true,
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "number",
                "null"
              ],
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "number",
                "null"
              ],
              "default": 20
            },
            "required": false,
            "name": "pageSize",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "bucketSlug",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Search results"
          }
        }
      }
    },
    "/api/v1/files/by-context": {
      "get": {
        "tags": [
          "Files"
        ],
        "summary": "Query files by context key/value",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "bucketSlug",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "contextKey",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "contextValue",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "number",
                "null"
              ],
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "number",
                "null"
              ],
              "default": 20
            },
            "required": false,
            "name": "pageSize",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Files matching context"
          }
        }
      }
    },
    "/api/v1/upload": {
      "post": {
        "tags": [
          "Upload"
        ],
        "summary": "Upload a single file",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "description": "File binary"
                  },
                  "bucketSlug": {
                    "type": "string",
                    "description": "Target bucket",
                    "example": "products"
                  },
                  "visibility": {
                    "type": "string",
                    "enum": [
                      "PUBLIC",
                      "PRIVATE"
                    ]
                  },
                  "context": {
                    "type": "string",
                    "description": "JSON string with arbitrary metadata"
                  },
                  "tags": {
                    "type": "string",
                    "description": "Comma-separated tags"
                  },
                  "category": {
                    "type": "string"
                  }
                },
                "required": [
                  "file",
                  "bucketSlug"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "File uploaded"
          },
          "400": {
            "description": "Validation error"
          },
          "413": {
            "description": "File too large"
          },
          "422": {
            "description": "Virus detected"
          }
        }
      }
    },
    "/api/v1/batch-upload": {
      "post": {
        "tags": [
          "Upload"
        ],
        "summary": "Upload multiple files (max 10)",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Batch results"
          }
        }
      }
    },
    "/api/v1/signed-url": {
      "post": {
        "tags": [
          "Files"
        ],
        "summary": "Generate a signed URL for private file access",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "fileId": {
                    "type": "string"
                  },
                  "expiryMinutes": {
                    "type": "number",
                    "default": 15,
                    "description": "URL expiry in minutes (max 1440)"
                  }
                },
                "required": [
                  "fileId"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed URL generated"
          }
        }
      }
    },
    "/api/v1/api-keys": {
      "get": {
        "tags": [
          "API Keys"
        ],
        "summary": "List API keys (admin only)",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "API key list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ApiKey"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginationMeta"
                    }
                  },
                  "required": [
                    "success",
                    "data",
                    "meta"
                  ]
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "API Keys"
        ],
        "summary": "Create API key (admin only). Returns plaintext key ONCE.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "permissions": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "read",
                        "write",
                        "delete",
                        "admin"
                      ]
                    }
                  },
                  "allowedBuckets": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "default": []
                  },
                  "rateLimit": {
                    "type": "number",
                    "default": 100
                  },
                  "expiresInDays": {
                    "type": "number"
                  }
                },
                "required": [
                  "name",
                  "permissions"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "API key created with plaintext key"
          }
        }
      }
    },
    "/api/v1/audit-log": {
      "get": {
        "tags": [
          "Audit"
        ],
        "summary": "Query audit log (admin only)",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": [
                "number",
                "null"
              ],
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "number",
                "null"
              ],
              "default": 50
            },
            "required": false,
            "name": "pageSize",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "action",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "entity",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Audit log entries"
          }
        }
      }
    },
    "/api/v1/webhooks": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "List webhooks (admin only)",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": [
                "number",
                "null"
              ],
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "number",
                "null"
              ],
              "default": 20
            },
            "required": false,
            "name": "pageSize",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated webhook list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Webhook"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginationMeta"
                    }
                  },
                  "required": [
                    "success",
                    "data",
                    "meta"
                  ]
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Register a new webhook (admin). Returns signing secret ONCE.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "HTTPS endpoint URL"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Events to subscribe: file.uploaded, file.updated, file.deleted, bucket.created, bucket.updated, bucket.deleted",
                    "example": [
                      "file.uploaded",
                      "file.deleted"
                    ]
                  },
                  "description": {
                    "type": "string"
                  }
                },
                "required": [
                  "url",
                  "events"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook created with signing secret"
          },
          "400": {
            "description": "Validation error"
          }
        }
      }
    },
    "/api/v1/webhooks/{id}": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Get webhook by ID",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook details"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "put": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Update webhook (url, events, isActive)",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "description": {
                    "type": "string"
                  },
                  "isActive": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook updated"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Delete webhook",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook deleted"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/v1/webhooks/{id}/deliveries": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "List webhook deliveries with status",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": [
                "number",
                "null"
              ],
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "number",
                "null"
              ],
              "default": 20
            },
            "required": false,
            "name": "pageSize",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "success",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "event",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated delivery list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookDelivery"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginationMeta"
                    }
                  },
                  "required": [
                    "success",
                    "data",
                    "meta"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/webhooks/{id}/test": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Send a test event to verify webhook endpoint",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Test delivery dispatched"
          },
          "404": {
            "description": "Webhook not found"
          }
        }
      }
    }
  },
  "webhooks": {}
}