{
  "openapi": "3.1.0",
  "info": {
    "title": "Flare named-token API",
    "version": "1.0.0",
    "summary": "Account-scoped file uploads, file metadata, short links, and outbound file-ready events.",
    "description": "Documents the current unversioned routes in Flare, not an /api/v1 prefix. Replace the server URL with your own instance. Only named-token-supported endpoints appear under paths; browser-session/admin routes are documented separately. Bearer tokens use application scopes, not OAuth. Upload sizes are bytes; list-file sizes are MiB. Some responses intentionally omit success or data wrappers. The info version versions this document; webhook payload version is independently 1. Every operation requires both its token scope and the owner’s current role permission (x-flare-permissions), rechecked on each request. Role/user administration and authenticator/passkey management remain browser-session-only. Two-factor authentication and passkeys protect interactive sign-in. The optional account passkey requirement blocks password and SSO sign-in and has separate emergency recovery codes; its management and recovery flow are also browser-only. Dedicated recovery identifies the current email case-insensitively, requires an unambiguous account match, and rejects conflicting legacy addresses without consuming a code. API tokens remain independent credentials with the same scopes, limits, and verification requirements. Browser security routes are documented in the handbook at api/security, not in paths. Personal session history/revocation and instance audit queries also require a browser session; audit reads require audit.read. These routes never accept named tokens or the legacy upload credential. See the x-flare-browser-session-api extension and handbook api/activity. Revoking browser sessions does not revoke API credentials. Audit recording adds no webhook event type. Owner-library archive browsing, extraction, and creation require an owner browser session. Separate share-page manifest and entry POST routes follow file visibility/password rules, allowing anonymous reads of unprotected public archives; they accept passwords in request bodies and do not provide extraction. Shared archive body validation and file authorization complete before archive-processing capacity is reserved; body reading has a separate five-second deadline and 16 KiB limit. All archive routes reject Authorization headers, are documented at api/archives, and are excluded from named-token paths and scopes. GET /api/files/tags reads current tag membership for dashboard selections; it accepts an account browser session or legacy upload credential, not named tokens, and is documented at api/files#read-current-tags-for-a-selection rather than in paths.",
    "license": {
      "name": "MIT",
      "url": "https://github.com/FlintSH/Flare/blob/main/LICENSE"
    }
  },
  "servers": [
    {
      "url": "https://files.example.com",
      "description": "Replace with your Flare instance origin."
    }
  ],
  "tags": [
    {
      "name": "Files",
      "description": "Own-account uploads and metadata. Named tokens do not authorize private content downloads or file deletion."
    },
    { "name": "Short links", "description": "Own-account short links." },
    {
      "name": "Webhooks",
      "description": "Outbound requests from Flare to a receiver you control."
    }
  ],
  "security": [{ "BearerAuth": [] }],
  "paths": {
    "/api/files": {
      "get": {
        "operationId": "listFiles",
        "summary": "List and search your files",
        "tags": ["Files"],
        "description": "Requires the files:read scope. Pagination is offset-based except for image-anchor requests. File sizes in this response are MiB, not bytes. Results include only this account and use Cache-Control: private, no-store. The token owner must currently have files.read; role revocation applies on the next request. The optional snapshot upload-time ceiling supports bounded timeline windows; it does not freeze deletions or edits.",
        "security": [{ "BearerAuth": [] }],
        "x-flare-scope": "files:read",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/FileListResponse" },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "cm_example",
                      "name": "screenshot.png",
                      "urlPath": "/abc123/screenshot.png",
                      "mimeType": "image/png",
                      "size": 0.1953125,
                      "uploadedAt": "2026-09-20T12:00:00.000Z",
                      "visibility": "PUBLIC",
                      "views": 0,
                      "downloads": 0,
                      "folderId": null,
                      "user": { "urlId": "abc123" },
                      "tags": [],
                      "hasPassword": false,
                      "expiresAt": null
                    }
                  ],
                  "pagination": {
                    "total": 1,
                    "pageCount": 1,
                    "page": 1,
                    "limit": 24
                  }
                }
              }
            },
            "headers": {
              "Cache-Control": {
                "schema": { "type": "string" },
                "example": "private, no-store"
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing/invalid token, expired/revoked token, missing scope, or required email verification.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Unauthorized" }
              }
            }
          },
          "400": {
            "description": "Invalid page, limit, gallery pair, date, snapshot, visibility, or selected-ID filter.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          },
          "404": {
            "description": "Owned resource or upload session not found.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          },
          "500": {
            "description": "The operation could not be completed.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          },
          "403": {
            "description": "Authenticated account lacks the required current role permission.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": {
                  "error": "You do not have permission to perform this action."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "ids",
            "in": "query",
            "required": false,
            "description": "One comma-separated list of 1–100 distinct file IDs, each 1–128 characters without whitespace or ASCII control characters. Repeated ids parameters, duplicates, and empty members are invalid. Combined with ownership and every other filter; missing and unowned IDs are omitted. Normal sort and pagination still apply; use limit=100 to refresh a full selection.",
            "style": "form",
            "explode": false,
            "schema": {
              "type": "array",
              "minItems": 1,
              "maxItems": 100,
              "uniqueItems": true,
              "items": {
                "type": "string",
                "minLength": 1,
                "maxLength": 128,
                "pattern": "^[^\\s,\\u0000-\\u001f\\u007f]+$"
              }
            },
            "example": ["FILE_ID_1", "FILE_ID_2"]
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Positive page number.",
            "schema": { "type": "integer", "minimum": 1, "default": 1 }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Positive integer. Values above 100 are capped at 100.",
            "schema": { "type": "integer", "minimum": 1, "default": 24 }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Case-insensitive filename or OCR-text match.",
            "schema": { "type": "string" }
          },
          {
            "name": "sortBy",
            "in": "query",
            "required": false,
            "description": "Sort order; unknown values fall back to newest.",
            "schema": {
              "type": "string",
              "default": "newest",
              "enum": [
                "newest",
                "oldest",
                "largest",
                "smallest",
                "name",
                "most-viewed",
                "least-viewed",
                "most-downloaded",
                "least-downloaded"
              ]
            }
          },
          {
            "name": "types",
            "in": "query",
            "required": false,
            "description": "Comma-separated exact MIME types, for example image/png,image/jpeg.",
            "schema": { "type": "string" }
          },
          {
            "name": "dateFrom",
            "in": "query",
            "required": false,
            "description": "Inclusive uploaded-at lower bound; use an ISO 8601 timestamp with timezone.",
            "schema": { "type": "string" }
          },
          {
            "name": "dateTo",
            "in": "query",
            "required": false,
            "description": "Inclusive upper bound. A date-only value includes the final day in server-local time; explicit timestamps preserve their instant.",
            "schema": { "type": "string" }
          },
          {
            "name": "visibility",
            "in": "query",
            "required": false,
            "description": "Comma-separated public, private, hasPassword. Conditions within this filter are ORed.",
            "schema": { "type": "string" }
          },
          {
            "name": "folder",
            "in": "query",
            "required": false,
            "description": "Owned folder ID or unfiled. Direct folder membership, without descendants.",
            "schema": { "type": "string" }
          },
          {
            "name": "tag",
            "in": "query",
            "required": false,
            "description": "Owned tag ID or untagged. Excluded tags do not count.",
            "schema": { "type": "string" }
          },
          {
            "name": "galleryAnchor",
            "in": "query",
            "required": false,
            "description": "Image file ID in the current filter; requires galleryDirection. Returns adjacent images, excluding anchor.",
            "schema": { "type": "string" }
          },
          {
            "name": "galleryDirection",
            "in": "query",
            "required": false,
            "description": "Requires galleryAnchor. Anchored responses include pagination.offset and only image files.",
            "schema": { "type": "string", "enum": ["next", "previous"] }
          },
          {
            "name": "snapshot",
            "in": "query",
            "required": false,
            "description": "Inclusive upload timestamp ceiling returned by GET /api/files/timeline. Reuse it unchanged across window reads; it is not a durable snapshot.",
            "schema": { "type": "string", "format": "date-time" }
          }
        ],
        "x-flare-permissions": ["files.read"]
      },
      "post": {
        "operationId": "uploadFile",
        "summary": "Upload one file",
        "tags": ["Files"],
        "description": "Requires the files:upload scope. Stream one multipart file. Select profile/folder in headers or query and naming in profile/account defaults before sending bytes. All upload limits and quota apply; accounts with quotas.bypass or Administrator are exempt from the default quota but not file-size limits. No multipart idempotency key is implemented. The token owner must currently have files.upload; role revocation applies on the next request. Without files.share, new uploads are private. Expiration requires files.delete for DELETE or files.share for SET_PRIVATE, including inherited defaults; missing action permission returns 403. Uploads applying tags/folders need tags.manage/folders.manage.",
        "security": [{ "BearerAuth": [] }],
        "x-flare-scope": "files:upload",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "$ref": "#/components/schemas/UploadLinks" },
                    "success": { "const": true, "type": "boolean" }
                  },
                  "required": ["data", "success"]
                },
                "example": {
                  "success": true,
                  "data": {
                    "url": "https://files.example.com/abc123/screenshot.png",
                    "pageUrl": "https://files.example.com/abc123/screenshot.png",
                    "rawUrl": "https://files.example.com/api/files/abc123/screenshot.png",
                    "downloadUrl": "https://files.example.com/api/files/cm_example/download",
                    "copyText": "https://files.example.com/abc123/screenshot.png",
                    "name": "screenshot.png",
                    "size": 204800,
                    "type": "image/png"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing/invalid token, expired/revoked token, missing scope, or required email verification.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Unauthorized" }
              }
            }
          },
          "400": {
            "description": "Invalid request/options, part list, file type, or expiration.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "403": {
            "description": "Required current role permission is missing. Profile binding, ownership, or final token validity check failed.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Owned resource or upload session not found.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "413": {
            "description": "File size, quota, or part-size limit exceeded.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "500": {
            "description": "The operation could not be completed.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "429": {
            "description": "Upload-start rate limit: 30 requests per minute per client IP per application process.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Too many requests" }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait; currently 60.",
                "schema": { "type": "string" },
                "example": "60"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "X-Upload-Profile",
            "in": "header",
            "required": false,
            "description": "Owned profile ID; none bypasses default. Must agree with query/body selection.",
            "schema": { "type": "string" }
          },
          {
            "name": "X-Upload-Folder",
            "in": "header",
            "required": false,
            "description": "Owned destination folder ID; none means unfiled. Must agree with query/body selection.",
            "schema": { "type": "string" }
          },
          {
            "name": "profileId",
            "in": "query",
            "required": false,
            "description": "Owned profile ID or none. Must agree with header.",
            "schema": { "type": "string" }
          },
          {
            "name": "folderId",
            "in": "query",
            "required": false,
            "description": "Owned folder ID or none. Must agree with header.",
            "schema": { "type": "string" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/MultipartUploadRequest"
              }
            }
          }
        },
        "x-flare-permissions": ["files.upload"]
      }
    },
    "/api/files/types": {
      "get": {
        "operationId": "listFileTypes",
        "summary": "List MIME types present in your account",
        "tags": ["Files"],
        "description": "Requires the files:read scope. Sorted distinct stored MIME types, not an allowlist of permitted upload types. The token owner must currently have files.read; role revocation applies on the next request.",
        "security": [{ "BearerAuth": [] }],
        "x-flare-scope": "files:read",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/FileTypesResponse" },
                "example": {
                  "success": true,
                  "data": { "types": ["application/pdf", "image/png"] }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing/invalid token, expired/revoked token, missing scope, or required email verification.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Unauthorized" }
              }
            }
          },
          "500": {
            "description": "The operation could not be completed.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          },
          "403": {
            "description": "Authenticated account lacks the required current role permission.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": {
                  "error": "You do not have permission to perform this action."
                }
              }
            }
          }
        },
        "x-flare-permissions": ["files.read"]
      }
    },
    "/api/files/chunks": {
      "post": {
        "operationId": "initializeChunkUpload",
        "summary": "Initialize a chunk upload",
        "tags": ["Files"],
        "description": "Requires the files:upload scope. Size is positive bytes. Snapshots upload options. Unknown JSON properties are rejected except the retired copyFormat field, which is ignored. Sessions depend on application temporary storage. The token owner must currently have files.upload; role revocation applies on the next request. Without files.share, new uploads are private. Expiration requires files.delete for DELETE or files.share for SET_PRIVATE, including inherited defaults; missing action permission returns 403. Uploads applying tags/folders need tags.manage/folders.manage. Records the actual storage target in server-side session metadata; clients cannot override that provenance.",
        "security": [{ "BearerAuth": [] }],
        "x-flare-scope": "files:upload",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ChunkInitResponse" },
                "example": {
                  "data": {
                    "uploadId": "0123456789abcdef0123456789abcdef",
                    "fileKey": "uploads/abc123/object/archive.zip"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing/invalid token, expired/revoked token, missing scope, or required email verification.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Unauthorized" }
              }
            }
          },
          "400": {
            "description": "Invalid request/options, part list, file type, or expiration.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "403": {
            "description": "Required current role permission is missing. Profile binding, ownership, or final token validity check failed.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Owned resource or upload session not found.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "413": {
            "description": "File size, quota, or part-size limit exceeded.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "500": {
            "description": "The operation could not be completed.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "429": {
            "description": "Upload-start rate limit: 30 requests per minute per client IP per application process.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Too many requests" }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait; currently 60.",
                "schema": { "type": "string" },
                "example": "60"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "X-Upload-Profile",
            "in": "header",
            "required": false,
            "description": "Owned profile ID; none bypasses default. Must agree with query/body selection.",
            "schema": { "type": "string" }
          },
          {
            "name": "X-Upload-Folder",
            "in": "header",
            "required": false,
            "description": "Owned destination folder ID; none means unfiled. Must agree with query/body selection.",
            "schema": { "type": "string" }
          },
          {
            "name": "profileId",
            "in": "query",
            "required": false,
            "description": "Owned profile ID or none. Must agree with header.",
            "schema": { "type": "string" }
          },
          {
            "name": "folderId",
            "in": "query",
            "required": false,
            "description": "Owned folder ID or none. Must agree with header.",
            "schema": { "type": "string" }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "JSON request.",
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ChunkInitRequest" }
            }
          }
        },
        "x-flare-permissions": ["files.upload"]
      },
      "get": {
        "operationId": "getChunkPartUrlCompatibility",
        "summary": "Get a part upload URL (query form)",
        "tags": ["Files"],
        "description": "Requires the files:upload scope. Not an upload-status or list-parts route. Refreshes activity. Both url and presignedUrl contain the same value. Local storage returns a local:// marker; use the authenticated PUT-part route. The token owner must currently have files.upload; role revocation applies on the next request. Without files.share, new uploads are private. Expiration requires files.delete for DELETE or files.share for SET_PRIVATE, including inherited defaults; missing action permission returns 403. Uploads applying tags/folders need tags.manage/folders.manage. Returns 409 if the actual storage target changed or upload metadata lacks provenance (including sessions started before the provenance upgrade). Initialize a new upload and resend its parts; do not reuse the old uploadId.",
        "security": [{ "BearerAuth": [] }],
        "x-flare-scope": "files:upload",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompatibilityPartUrlResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing/invalid token, expired/revoked token, missing scope, or required email verification.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Unauthorized" }
              }
            }
          },
          "400": {
            "description": "Invalid request/options, part list, file type, or expiration.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "403": {
            "description": "Required current role permission is missing. Profile binding, ownership, or final token validity check failed.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Owned resource or upload session not found.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "409": {
            "description": "Storage configuration changed or upload already completed.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "500": {
            "description": "The operation could not be completed.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "uploadId",
            "in": "query",
            "required": true,
            "description": "Upload session ID.",
            "schema": { "type": "string", "pattern": "^[a-z0-9]{1,100}$" }
          },
          {
            "name": "partNumber",
            "in": "query",
            "required": true,
            "description": "Part number.",
            "schema": { "type": "integer", "minimum": 1, "maximum": 10000 }
          }
        ],
        "x-flare-permissions": ["files.upload"]
      },
      "put": {
        "operationId": "completeChunkUploadCompatibility",
        "summary": "Complete a chunk upload (wrapped response)",
        "tags": ["Files"],
        "description": "Requires the files:upload scope. Same finalization as the dedicated completion route but requires uploadId in JSON and wraps output in data. Completion retries return the same committed file while session metadata exists. Does not refresh the inactivity timestamp. The token owner must currently have files.upload; role revocation applies on the next request. Without files.share, new uploads are private. Expiration requires files.delete for DELETE or files.share for SET_PRIVATE, including inherited defaults; missing action permission returns 403. Uploads applying tags/folders need tags.manage/folders.manage. Returns 409 if the actual storage target changed or upload metadata lacks provenance (including sessions started before the provenance upgrade). Initialize a new upload and resend its parts; do not reuse the old uploadId.",
        "security": [{ "BearerAuth": [] }],
        "x-flare-scope": "files:upload",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "$ref": "#/components/schemas/UploadLinks" }
                  },
                  "required": ["data"]
                },
                "example": {
                  "data": {
                    "url": "https://files.example.com/abc123/screenshot.png",
                    "pageUrl": "https://files.example.com/abc123/screenshot.png",
                    "rawUrl": "https://files.example.com/api/files/abc123/screenshot.png",
                    "downloadUrl": "https://files.example.com/api/files/cm_example/download",
                    "copyText": "https://files.example.com/abc123/screenshot.png",
                    "name": "screenshot.png",
                    "size": 204800,
                    "type": "image/png"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing/invalid token, expired/revoked token, missing scope, or required email verification.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Unauthorized" }
              }
            }
          },
          "400": {
            "description": "Invalid request/options, part list, file type, or expiration.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "403": {
            "description": "Required current role permission is missing. Profile binding, ownership, or final token validity check failed.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Owned resource or upload session not found.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "409": {
            "description": "Storage configuration changed or upload already completed.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "413": {
            "description": "File size, quota, or part-size limit exceeded.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "500": {
            "description": "The operation could not be completed.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "description": "JSON request.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChunkCompleteCompatibilityRequest"
              }
            }
          }
        },
        "x-flare-permissions": ["files.upload"]
      }
    },
    "/api/files/chunks/{uploadId}/part/{partNumber}": {
      "parameters": [
        {
          "name": "uploadId",
          "in": "path",
          "required": true,
          "description": "Upload session ID. Expires after one hour of inactivity.",
          "schema": { "type": "string", "pattern": "^[a-z0-9]{1,100}$" }
        },
        {
          "name": "partNumber",
          "in": "path",
          "required": true,
          "description": "Part number.",
          "schema": { "type": "integer", "minimum": 1, "maximum": 10000 }
        }
      ],
      "get": {
        "operationId": "getChunkPartUrl",
        "summary": "Get a part upload URL",
        "tags": ["Files"],
        "description": "Requires the files:upload scope. S3 URL expires after one hour. PUT bytes to it without the Flare bearer token and retain the ETag response header. Local storage returns a local:// marker; use PUT to this Flare route instead. Refreshes session activity. The token owner must currently have files.upload; role revocation applies on the next request. Without files.share, new uploads are private. Expiration requires files.delete for DELETE or files.share for SET_PRIVATE, including inherited defaults; missing action permission returns 403. Uploads applying tags/folders need tags.manage/folders.manage. Returns 409 if the actual storage target changed or upload metadata lacks provenance (including sessions started before the provenance upgrade). Initialize a new upload and resend its parts; do not reuse the old uploadId.",
        "security": [{ "BearerAuth": [] }],
        "x-flare-scope": "files:upload",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PartUrlResponse" }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing/invalid token, expired/revoked token, missing scope, or required email verification.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Unauthorized" }
              }
            }
          },
          "400": {
            "description": "Invalid request/options, part list, file type, or expiration.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "403": {
            "description": "Required current role permission is missing. Profile binding, ownership, or final token validity check failed.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Owned resource or upload session not found.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "409": {
            "description": "Storage configuration changed or upload already completed.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "500": {
            "description": "The operation could not be completed.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        },
        "x-flare-permissions": ["files.upload"]
      },
      "put": {
        "operationId": "uploadChunkPart",
        "summary": "Upload raw part bytes through Flare",
        "tags": ["Files"],
        "description": "Requires the files:upload scope. Works with local and S3 storage. At most 64 MiB and no more than declared total size. Save lowercase data.etag as uppercase ETag in the completion body. Refreshes session activity. The token owner must currently have files.upload; role revocation applies on the next request. Without files.share, new uploads are private. Expiration requires files.delete for DELETE or files.share for SET_PRIVATE, including inherited defaults; missing action permission returns 403. Uploads applying tags/folders need tags.manage/folders.manage. Returns 409 if the actual storage target changed or upload metadata lacks provenance (including sessions started before the provenance upgrade). Initialize a new upload and resend its parts; do not reuse the old uploadId.",
        "security": [{ "BearerAuth": [] }],
        "x-flare-scope": "files:upload",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PartUploadResponse" },
                "example": { "data": { "etag": "\"provider-etag\"" } }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing/invalid token, expired/revoked token, missing scope, or required email verification.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Unauthorized" }
              }
            }
          },
          "400": {
            "description": "Invalid request/options, part list, file type, or expiration.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "403": {
            "description": "Required current role permission is missing. Profile binding, ownership, or final token validity check failed.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Owned resource or upload session not found.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "409": {
            "description": "Storage configuration changed or upload already completed.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "413": {
            "description": "File size, quota, or part-size limit exceeded.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "500": {
            "description": "The operation could not be completed.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary",
                "description": "Raw part bytes, not multipart form data."
              }
            }
          }
        },
        "x-flare-permissions": ["files.upload"]
      }
    },
    "/api/files/chunks/{uploadId}/complete": {
      "parameters": [
        {
          "name": "uploadId",
          "in": "path",
          "required": true,
          "description": "Upload session ID. Expires after one hour of inactivity.",
          "schema": { "type": "string", "pattern": "^[a-z0-9]{1,100}$" }
        }
      ],
      "post": {
        "operationId": "completeChunkUpload",
        "summary": "Complete a chunk upload (unwrapped response)",
        "tags": ["Files"],
        "description": "Requires the files:upload scope. Returns upload links directly without a data/success wrapper. Unique parts are sorted by PartNumber. Actual size must equal declared size. Profile, folder, and naming cannot change; allowed sharing overrides apply. Repeated completion returns the existing file while metadata remains valid. No chunk cancellation endpoint is implemented. The token owner must currently have files.upload; role revocation applies on the next request. Without files.share, new uploads are private. Expiration requires files.delete for DELETE or files.share for SET_PRIVATE, including inherited defaults; missing action permission returns 403. Uploads applying tags/folders need tags.manage/folders.manage. Returns 409 if the actual storage target changed or upload metadata lacks provenance (including sessions started before the provenance upgrade). Initialize a new upload and resend its parts; do not reuse the old uploadId.",
        "security": [{ "BearerAuth": [] }],
        "x-flare-scope": "files:upload",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/UploadLinks" },
                "example": {
                  "url": "https://files.example.com/abc123/screenshot.png",
                  "pageUrl": "https://files.example.com/abc123/screenshot.png",
                  "rawUrl": "https://files.example.com/api/files/abc123/screenshot.png",
                  "downloadUrl": "https://files.example.com/api/files/cm_example/download",
                  "copyText": "https://files.example.com/abc123/screenshot.png",
                  "name": "screenshot.png",
                  "size": 204800,
                  "type": "image/png"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing/invalid token, expired/revoked token, missing scope, or required email verification.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Unauthorized" }
              }
            }
          },
          "400": {
            "description": "Invalid request/options, part list, file type, or expiration.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "403": {
            "description": "Required current role permission is missing. Profile binding, ownership, or final token validity check failed.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Owned resource or upload session not found.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "409": {
            "description": "Storage configuration changed or upload already completed.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "413": {
            "description": "File size, quota, or part-size limit exceeded.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "500": {
            "description": "The operation could not be completed.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "description": "JSON request.",
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ChunkCompleteRequest" }
            }
          }
        },
        "x-flare-permissions": ["files.upload"]
      }
    },
    "/api/urls": {
      "get": {
        "operationId": "listShortLinks",
        "summary": "List your short links",
        "tags": ["Short links"],
        "description": "Requires the urls:read scope. Newest first; no pagination or filtering parameters. The token owner must currently have links.read; role revocation applies on the next request.",
        "security": [{ "BearerAuth": [] }],
        "x-flare-scope": "urls:read",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShortLinkListResponse"
                },
                "example": {
                  "success": true,
                  "data": {
                    "urls": [
                      {
                        "id": "cm_link",
                        "shortCode": "aB3_dE",
                        "targetUrl": "https://example.com/a/long/path",
                        "clicks": 0,
                        "createdAt": "2026-09-20T12:00:00.000Z",
                        "userId": "cm_account"
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing/invalid token, expired/revoked token, missing scope, or required email verification.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Unauthorized" }
              }
            }
          },
          "500": {
            "description": "The operation could not be completed.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          },
          "403": {
            "description": "Authenticated account lacks the required current role permission.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": {
                  "error": "You do not have permission to perform this action."
                }
              }
            }
          }
        },
        "x-flare-permissions": ["links.read"]
      },
      "post": {
        "operationId": "createShortLink",
        "summary": "Create a short link",
        "tags": ["Short links"],
        "description": "Requires the urls:write scope. Generates a unique six-character code. Build the public link as instance origin + /u/ + shortCode. No custom code, expiration, password, or destination editing parameter. Repeated creation can produce another link. Malformed JSON currently reaches the 500 handler. The token owner must currently have links.create; role revocation applies on the next request.",
        "security": [{ "BearerAuth": [] }],
        "x-flare-scope": "urls:write",
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "$ref": "#/components/schemas/ShortLink" },
                    "success": { "const": true, "type": "boolean" }
                  },
                  "required": ["data", "success"]
                },
                "example": {
                  "success": true,
                  "data": {
                    "id": "cm_link",
                    "shortCode": "aB3_dE",
                    "targetUrl": "https://example.com/a/long/path",
                    "clicks": 0,
                    "createdAt": "2026-09-20T12:00:00.000Z",
                    "userId": "cm_account"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing/invalid token, expired/revoked token, missing scope, or required email verification.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Unauthorized" }
              }
            }
          },
          "400": {
            "description": "Invalid request/options, part list, file type, or expiration.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          },
          "500": {
            "description": "The operation could not be completed.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          },
          "403": {
            "description": "Authenticated account lacks the required current role permission.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": {
                  "error": "You do not have permission to perform this action."
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "description": "JSON request.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateShortLinkRequest"
              }
            }
          }
        },
        "x-flare-permissions": ["links.create"]
      }
    },
    "/api/urls/{id}": {
      "delete": {
        "operationId": "deleteShortLink",
        "summary": "Delete your short link",
        "tags": ["Short links"],
        "description": "Requires urls:write. Use the link record ID, not its shortCode. The token owner must currently have links.delete; role revocation applies on the next request.",
        "security": [{ "BearerAuth": [] }],
        "x-flare-scope": "urls:write",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Short-link record ID.",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "204": { "description": "Deleted. No response body." },
          "401": {
            "description": "Unauthorized: missing/invalid token, expired/revoked token, missing scope, or required email verification.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Unauthorized" }
              }
            }
          },
          "403": {
            "description": "Required current role permission is missing. Link belongs to another account.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" },
                "example": { "success": false, "error": "Unauthorized" }
              }
            }
          },
          "404": {
            "description": "Link does not exist.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" },
                "example": { "success": false, "error": "URL not found" }
              }
            }
          },
          "500": {
            "description": "Internal server error.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          }
        },
        "x-flare-permissions": ["links.delete"]
      }
    },
    "/api/files/timeline": {
      "get": {
        "operationId": "getFileTimeline",
        "summary": "Count file timeline buckets across your account",
        "tags": ["Files"],
        "description": "Database-aggregated counts only; no file metadata. Uses the same account-scoped filters as GET /api/files. Returns a fresh upload-time snapshot ceiling and nonempty calendar buckets, ordered newest or oldest. For non-date sorts returns one undated bucket. Fetch each visible window through GET /api/files with the same filters, snapshot, and date range intersected with the bucket. The snapshot does not freeze edits, deletions, or backdated inserts.",
        "security": [{ "BearerAuth": [] }],
        "x-flare-scope": "files:read",
        "x-flare-permissions": ["files.read"],
        "parameters": [
          {
            "name": "ids",
            "in": "query",
            "required": false,
            "description": "One comma-separated list of 1–100 distinct file IDs, each 1–128 characters without whitespace or ASCII control characters. Repeated ids parameters, duplicates, and empty members are invalid. Combined with ownership and every other filter; missing and unowned IDs are omitted. Normal sort and pagination still apply; use limit=100 to refresh a full selection.",
            "style": "form",
            "explode": false,
            "schema": {
              "type": "array",
              "minItems": 1,
              "maxItems": 100,
              "uniqueItems": true,
              "items": {
                "type": "string",
                "minLength": 1,
                "maxLength": 128,
                "pattern": "^[^\\s,\\u0000-\\u001f\\u007f]+$"
              }
            },
            "example": ["FILE_ID_1", "FILE_ID_2"]
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Case-insensitive filename or OCR-text match.",
            "schema": { "type": "string" }
          },
          {
            "name": "sortBy",
            "in": "query",
            "required": false,
            "description": "Sort order; unknown values fall back to newest.",
            "schema": {
              "type": "string",
              "default": "newest",
              "enum": [
                "newest",
                "oldest",
                "largest",
                "smallest",
                "name",
                "most-viewed",
                "least-viewed",
                "most-downloaded",
                "least-downloaded"
              ]
            }
          },
          {
            "name": "types",
            "in": "query",
            "required": false,
            "description": "Comma-separated exact MIME types, for example image/png,image/jpeg.",
            "schema": { "type": "string" }
          },
          {
            "name": "dateFrom",
            "in": "query",
            "required": false,
            "description": "Inclusive uploaded-at lower bound; use an ISO 8601 timestamp with timezone.",
            "schema": { "type": "string" }
          },
          {
            "name": "dateTo",
            "in": "query",
            "required": false,
            "description": "Inclusive upper bound. A date-only value includes the final day in server-local time; explicit timestamps preserve their instant.",
            "schema": { "type": "string" }
          },
          {
            "name": "visibility",
            "in": "query",
            "required": false,
            "description": "Comma-separated public, private, hasPassword. Conditions within this filter are ORed.",
            "schema": { "type": "string" }
          },
          {
            "name": "folder",
            "in": "query",
            "required": false,
            "description": "Owned folder ID or unfiled. Direct folder membership, without descendants.",
            "schema": { "type": "string" }
          },
          {
            "name": "tag",
            "in": "query",
            "required": false,
            "description": "Owned tag ID or untagged. Excluded tags do not count.",
            "schema": { "type": "string" }
          },
          {
            "name": "groupBy",
            "in": "query",
            "required": false,
            "description": "Calendar grouping. none still aggregates by month but preserves groupBy=none in the response. Weeks start Monday. Non-date sorts always return one undated bucket.",
            "schema": {
              "type": "string",
              "enum": ["none", "month", "week", "year"],
              "default": "none"
            }
          },
          {
            "name": "timezone",
            "in": "query",
            "required": false,
            "description": "IANA time zone used for calendar boundaries, including daylight-saving transitions.",
            "schema": {
              "type": "string",
              "default": "UTC",
              "example": "America/Los_Angeles"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FileTimelineResponse"
                },
                "example": {
                  "success": true,
                  "data": {
                    "total": 100,
                    "snapshot": "2026-10-06T12:00:00.000Z",
                    "groupBy": "none",
                    "timezone": "UTC",
                    "buckets": [
                      {
                        "key": "2026-10-01T00:00:00.000Z",
                        "from": "2026-10-01T00:00:00.000Z",
                        "to": "2026-11-01T00:00:00.000Z",
                        "count": 100,
                        "offset": 0
                      }
                    ]
                  }
                }
              }
            },
            "headers": {
              "Cache-Control": {
                "schema": { "type": "string" },
                "example": "private, no-store"
              }
            }
          },
          "401": {
            "description": "Unauthorized: missing/invalid token, expired/revoked token, missing scope, or required email verification.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Unauthorized" }
              }
            }
          },
          "400": {
            "description": "Invalid groupBy, timezone, date, or visibility filter.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          },
          "500": {
            "description": "The operation could not be completed.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiError" }
              }
            }
          },
          "403": {
            "description": "Authenticated account lacks the required current role permission.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": {
                  "error": "You do not have permission to perform this action."
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "fileReady": {
      "post": {
        "operationId": "receiveFileReady",
        "summary": "Receive a signed file.ready event",
        "tags": ["Webhooks"],
        "security": [],
        "description": "Outbound request to your configured receiver, not an endpoint hosted by Flare. Validate HMAC-SHA256 over timestamp + dot + exact raw body using the full whsec_ secret. Deduplicate stable event IDs. Five total attempts; retry delays 1 minute, 5 minutes, 30 minutes, 2 hours. 2xx acknowledges; 408/429/5xx and connection failures retry. Other 3xx/4xx fail without retry. Queue capacity can skip events; file-access changes can cancel them. file.ready does not imply OCR completion.",
        "parameters": [
          {
            "name": "X-Flare-Event-Id",
            "in": "header",
            "required": true,
            "description": "Stable event ID; must equal body.id.",
            "schema": { "type": "string" }
          },
          {
            "name": "X-Flare-Timestamp",
            "in": "header",
            "required": true,
            "description": "Unix seconds for this attempt. Reject stale/future requests outside your accepted clock window.",
            "schema": { "type": "string", "pattern": "^[0-9]+$" }
          },
          {
            "name": "X-Flare-Signature",
            "in": "header",
            "required": true,
            "description": "v1= plus lowercase hex HMAC-SHA256. Compare in constant time against the original body bytes.",
            "schema": { "type": "string", "pattern": "^v1=[0-9a-f]{64}$" }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "At most 64 KiB. Tests add test: true and use test: event IDs; they do not create a file.",
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/FileReadyEvent" }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Event durably accepted (for example 204). Duplicate accepted events should also return 2xx."
          },
          "408": { "description": "Timeout; retry eligible." },
          "429": { "description": "Throttled; retry on Flare fixed schedule." },
          "5XX": { "description": "Receiver failure; retry eligible." },
          "default": {
            "description": "Other 3xx/4xx permanently fail this automatic cycle. Redirects are not followed."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "flr_…",
        "description": "Named API token from Profile → Integrations. Select files:read, files:upload, urls:read, urls:write as needed. Required scope is recorded in each operation description and x-flare-scope; HTTP bearer security arrays are empty because this is not OAuth. Missing scopes produce 401. Required owner permission is recorded in x-flare-permissions. Missing current role permission produces 403 after authentication succeeds. Administrator never expands token scopes."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": { "error": { "type": "string" } },
        "required": ["error"],
        "description": "Upload/authentication failures use this shape; no success property is required."
      },
      "ApiError": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "success": { "type": "boolean", "const": false }
        },
        "required": ["error", "success"]
      },
      "UploadLinks": {
        "type": "object",
        "properties": {
          "url": { "type": "string", "format": "uri" },
          "pageUrl": { "type": "string", "format": "uri" },
          "rawUrl": { "type": "string", "format": "uri" },
          "downloadUrl": { "type": "string", "format": "uri" },
          "copyText": { "type": "string", "format": "uri" },
          "name": { "type": "string" },
          "size": { "type": "integer", "minimum": 0, "description": "Bytes." },
          "type": { "type": "string" }
        },
        "required": [
          "url",
          "pageUrl",
          "rawUrl",
          "downloadUrl",
          "copyText",
          "name",
          "size",
          "type"
        ],
        "description": "URLs follow sharing access rules; they are not private-file access grants. url and copyText alias pageUrl. name is the display name; type is the MIME type.",
        "example": {
          "url": "https://files.example.com/abc123/screenshot.png",
          "pageUrl": "https://files.example.com/abc123/screenshot.png",
          "rawUrl": "https://files.example.com/api/files/abc123/screenshot.png",
          "downloadUrl": "https://files.example.com/api/files/cm_example/download",
          "copyText": "https://files.example.com/abc123/screenshot.png",
          "name": "screenshot.png",
          "size": 204800,
          "type": "image/png"
        }
      },
      "UploadOptions": {
        "type": "object",
        "properties": {
          "visibility": {
            "type": "string",
            "enum": ["PUBLIC", "PRIVATE"],
            "description": "Public without a profile override. Private content requires an eligible browser session; this bearer token does not authorize content downloads."
          },
          "expiration": {
            "type": "string",
            "enum": ["DISABLED", "HOUR", "DAY", "WEEK", "MONTH"],
            "description": "Relative expiration; omission inherits. DISABLED explicitly disables it. MONTH increments the UTC calendar month."
          },
          "expiryAction": {
            "type": "string",
            "enum": ["DELETE", "SET_PRIVATE"],
            "description": "Action when expiration is processed. Inherits the profile/account default."
          },
          "randomizeFileUrls": {
            "type": "boolean",
            "description": "Select before bytes are written. Supported in chunk initialization; multipart uploads use the selected profile/account setting."
          },
          "shareStyle": {
            "type": "string",
            "enum": ["minimal", "framed", "delivery"]
          },
          "tagIds": {
            "type": "array",
            "items": { "type": "string", "minLength": 1, "maxLength": 100 },
            "maxItems": 20,
            "description": "Owned tag IDs. Omission inherits selected profile tags."
          },
          "profileId": {
            "type": ["string", "null"],
            "minLength": 1,
            "maxLength": 100,
            "description": "Owned profile ID, or null to bypass the account default. A profile-bound token cannot switch profiles."
          },
          "folderId": {
            "type": ["string", "null"],
            "minLength": 1,
            "maxLength": 128,
            "description": "Owned destination folder ID, or null for unfiled. A request-only selection, fixed at initialization."
          },
          "password": {
            "type": ["string", "null"],
            "maxLength": 72,
            "description": "At most 72 UTF-8 bytes as well as 72 characters. Empty or null clears password protection. Not a saved profile option."
          },
          "expiresAt": {
            "type": ["string", "null"],
            "format": "date-time",
            "description": "Future ISO 8601 timestamp with timezone; null disables expiration. Overrides relative expiration. Forbidden for a profile-bound token."
          },
          "copyFormat": {
            "deprecated": true,
            "description": "Retired compatibility field. Any supplied value is discarded before validation."
          }
        },
        "additionalProperties": false,
        "description": "Omission inherits. Restrictions apply to profile-bound tokens and options fixed before bytes are written."
      },
      "MultipartUploadRequest": {
        "type": "object",
        "properties": {
          "visibility": {
            "type": "string",
            "enum": ["PUBLIC", "PRIVATE"],
            "description": "Public without a profile override. Private content requires an eligible browser session; this bearer token does not authorize content downloads."
          },
          "expiration": {
            "type": "string",
            "enum": ["DISABLED", "HOUR", "DAY", "WEEK", "MONTH"],
            "description": "Relative expiration; omission inherits. DISABLED explicitly disables it. MONTH increments the UTC calendar month."
          },
          "expiryAction": {
            "type": "string",
            "enum": ["DELETE", "SET_PRIVATE"],
            "description": "Action when expiration is processed. Inherits the profile/account default."
          },
          "randomizeFileUrls": {
            "type": "string",
            "enum": ["true", "false"],
            "description": "May only repeat the already selected naming option; set naming in the profile/account before upload."
          },
          "shareStyle": {
            "type": "string",
            "enum": ["minimal", "framed", "delivery"]
          },
          "tagIds": {
            "type": "string",
            "description": "JSON-encoded array of at most 20 owned tag IDs, for example [\"TAG_ID\"]."
          },
          "profileId": {
            "type": "string",
            "description": "Owned profile ID, or null to bypass the account default. A profile-bound token cannot switch profiles. Multipart uses an empty string or the literal string null for null. Select with X-Upload-Profile or the profileId query before sending bytes."
          },
          "folderId": {
            "type": "string",
            "description": "Owned destination folder ID, or null for unfiled. A request-only selection, fixed at initialization. Multipart uses an empty string or the literal string null for null. Select with X-Upload-Folder or the folderId query before sending bytes."
          },
          "password": {
            "type": "string",
            "description": "At most 72 UTF-8 bytes as well as 72 characters. Empty or null clears password protection. Not a saved profile option. Multipart uses an empty string or the literal string null for null."
          },
          "expiresAt": {
            "type": "string",
            "description": "Future ISO 8601 timestamp with timezone; null disables expiration. Overrides relative expiration. Forbidden for a profile-bound token. Multipart uses an empty string or the literal string null for null."
          },
          "file": {
            "type": "string",
            "format": "binary",
            "description": "One file. Let the HTTP client create the multipart boundary."
          }
        },
        "required": ["file"],
        "description": "One multipart file with optional string fields. Profile/folder/naming must be selected before streaming starts."
      },
      "ChunkInitRequest": {
        "type": "object",
        "properties": {
          "visibility": {
            "type": "string",
            "enum": ["PUBLIC", "PRIVATE"],
            "description": "Public without a profile override. Private content requires an eligible browser session; this bearer token does not authorize content downloads."
          },
          "expiration": {
            "type": "string",
            "enum": ["DISABLED", "HOUR", "DAY", "WEEK", "MONTH"],
            "description": "Relative expiration; omission inherits. DISABLED explicitly disables it. MONTH increments the UTC calendar month."
          },
          "expiryAction": {
            "type": "string",
            "enum": ["DELETE", "SET_PRIVATE"],
            "description": "Action when expiration is processed. Inherits the profile/account default."
          },
          "randomizeFileUrls": {
            "type": "boolean",
            "description": "Select before bytes are written. Supported in chunk initialization; multipart uploads use the selected profile/account setting."
          },
          "shareStyle": {
            "type": "string",
            "enum": ["minimal", "framed", "delivery"]
          },
          "tagIds": {
            "type": "array",
            "items": { "type": "string", "minLength": 1, "maxLength": 100 },
            "maxItems": 20,
            "description": "Owned tag IDs. Omission inherits selected profile tags."
          },
          "profileId": {
            "type": ["string", "null"],
            "minLength": 1,
            "maxLength": 100,
            "description": "Owned profile ID, or null to bypass the account default. A profile-bound token cannot switch profiles."
          },
          "folderId": {
            "type": ["string", "null"],
            "minLength": 1,
            "maxLength": 128,
            "description": "Owned destination folder ID, or null for unfiled. A request-only selection, fixed at initialization."
          },
          "password": {
            "type": ["string", "null"],
            "maxLength": 72,
            "description": "At most 72 UTF-8 bytes as well as 72 characters. Empty or null clears password protection. Not a saved profile option."
          },
          "expiresAt": {
            "type": ["string", "null"],
            "format": "date-time",
            "description": "Future ISO 8601 timestamp with timezone; null disables expiration. Overrides relative expiration. Forbidden for a profile-bound token."
          },
          "filename": { "type": "string", "minLength": 1, "maxLength": 255 },
          "mimeType": { "type": "string", "minLength": 1, "maxLength": 255 },
          "size": {
            "type": "integer",
            "minimum": 1,
            "description": "Total file bytes; verified after assembly."
          },
          "copyFormat": {
            "deprecated": true,
            "description": "Retired compatibility field. Any supplied value is discarded before validation."
          }
        },
        "required": ["filename", "mimeType", "size"],
        "additionalProperties": false
      },
      "ChunkInitResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "uploadId": { "type": "string", "pattern": "^[a-z0-9]{1,100}$" },
              "fileKey": {
                "type": "string",
                "description": "Opaque internal storage key, not a public URL."
              }
            },
            "required": ["uploadId", "fileKey"]
          }
        },
        "required": ["data"]
      },
      "ChunkCompleteRequest": {
        "type": "object",
        "properties": {
          "visibility": {
            "type": "string",
            "enum": ["PUBLIC", "PRIVATE"],
            "description": "Public without a profile override. Private content requires an eligible browser session; this bearer token does not authorize content downloads."
          },
          "expiration": {
            "type": "string",
            "enum": ["DISABLED", "HOUR", "DAY", "WEEK", "MONTH"],
            "description": "Relative expiration; omission inherits. DISABLED explicitly disables it. MONTH increments the UTC calendar month."
          },
          "expiryAction": {
            "type": "string",
            "enum": ["DELETE", "SET_PRIVATE"],
            "description": "Action when expiration is processed. Inherits the profile/account default."
          },
          "randomizeFileUrls": {
            "type": "boolean",
            "description": "Select before bytes are written. Supported in chunk initialization; multipart uploads use the selected profile/account setting."
          },
          "shareStyle": {
            "type": "string",
            "enum": ["minimal", "framed", "delivery"]
          },
          "tagIds": {
            "type": "array",
            "items": { "type": "string", "minLength": 1, "maxLength": 100 },
            "maxItems": 20,
            "description": "Owned tag IDs. Omission inherits selected profile tags."
          },
          "profileId": {
            "type": ["string", "null"],
            "minLength": 1,
            "maxLength": 100,
            "description": "Owned profile ID, or null to bypass the account default. A profile-bound token cannot switch profiles."
          },
          "folderId": {
            "type": ["string", "null"],
            "minLength": 1,
            "maxLength": 128,
            "description": "Owned destination folder ID, or null for unfiled. A request-only selection, fixed at initialization."
          },
          "password": {
            "type": ["string", "null"],
            "maxLength": 72,
            "description": "At most 72 UTF-8 bytes as well as 72 characters. Empty or null clears password protection. Not a saved profile option."
          },
          "expiresAt": {
            "type": ["string", "null"],
            "format": "date-time",
            "description": "Future ISO 8601 timestamp with timezone; null disables expiration. Overrides relative expiration. Forbidden for a profile-bound token."
          },
          "uploadId": {
            "type": "string",
            "description": "Required for PUT /api/files/chunks; ignored in favor of path ID on the dedicated completion route."
          },
          "parts": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "ETag": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 512,
                  "description": "Exact ETag returned by the provider, including any quote characters."
                },
                "PartNumber": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 10000
                }
              },
              "required": ["ETag", "PartNumber"],
              "additionalProperties": false
            },
            "minItems": 1,
            "maxItems": 10000,
            "description": "Unique part numbers. Flare sorts by PartNumber before assembling."
          },
          "copyFormat": {
            "deprecated": true,
            "description": "Retired compatibility field. Any supplied value is discarded before validation."
          }
        },
        "required": ["parts"],
        "additionalProperties": false
      },
      "ChunkCompleteCompatibilityRequest": {
        "type": "object",
        "properties": {
          "visibility": {
            "type": "string",
            "enum": ["PUBLIC", "PRIVATE"],
            "description": "Public without a profile override. Private content requires an eligible browser session; this bearer token does not authorize content downloads."
          },
          "expiration": {
            "type": "string",
            "enum": ["DISABLED", "HOUR", "DAY", "WEEK", "MONTH"],
            "description": "Relative expiration; omission inherits. DISABLED explicitly disables it. MONTH increments the UTC calendar month."
          },
          "expiryAction": {
            "type": "string",
            "enum": ["DELETE", "SET_PRIVATE"],
            "description": "Action when expiration is processed. Inherits the profile/account default."
          },
          "randomizeFileUrls": {
            "type": "boolean",
            "description": "Select before bytes are written. Supported in chunk initialization; multipart uploads use the selected profile/account setting."
          },
          "shareStyle": {
            "type": "string",
            "enum": ["minimal", "framed", "delivery"]
          },
          "tagIds": {
            "type": "array",
            "items": { "type": "string", "minLength": 1, "maxLength": 100 },
            "maxItems": 20,
            "description": "Owned tag IDs. Omission inherits selected profile tags."
          },
          "profileId": {
            "type": ["string", "null"],
            "minLength": 1,
            "maxLength": 100,
            "description": "Owned profile ID, or null to bypass the account default. A profile-bound token cannot switch profiles."
          },
          "folderId": {
            "type": ["string", "null"],
            "minLength": 1,
            "maxLength": 128,
            "description": "Owned destination folder ID, or null for unfiled. A request-only selection, fixed at initialization."
          },
          "password": {
            "type": ["string", "null"],
            "maxLength": 72,
            "description": "At most 72 UTF-8 bytes as well as 72 characters. Empty or null clears password protection. Not a saved profile option."
          },
          "expiresAt": {
            "type": ["string", "null"],
            "format": "date-time",
            "description": "Future ISO 8601 timestamp with timezone; null disables expiration. Overrides relative expiration. Forbidden for a profile-bound token."
          },
          "uploadId": {
            "type": "string",
            "description": "Required for PUT /api/files/chunks; ignored in favor of path ID on the dedicated completion route."
          },
          "parts": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "ETag": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 512,
                  "description": "Exact ETag returned by the provider, including any quote characters."
                },
                "PartNumber": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 10000
                }
              },
              "required": ["ETag", "PartNumber"],
              "additionalProperties": false
            },
            "minItems": 1,
            "maxItems": 10000,
            "description": "Unique part numbers. Flare sorts by PartNumber before assembling."
          },
          "copyFormat": {
            "deprecated": true,
            "description": "Retired compatibility field. Any supplied value is discarded before validation."
          }
        },
        "required": ["uploadId", "parts"],
        "additionalProperties": false
      },
      "PartUrlResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "url": {
                "type": "string",
                "description": "S3 presigned HTTPS upload URL (one-hour expiry), or local:// marker for local storage."
              }
            },
            "required": ["url"]
          }
        },
        "required": ["data"]
      },
      "CompatibilityPartUrlResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "url": { "type": "string" },
              "presignedUrl": {
                "type": "string",
                "description": "Alias of url."
              }
            },
            "required": ["url", "presignedUrl"]
          }
        },
        "required": ["data"]
      },
      "PartUploadResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "etag": {
                "type": "string",
                "description": "Supply this exact value as ETag at completion."
              }
            },
            "required": ["etag"]
          }
        },
        "required": ["data"]
      },
      "FileMetadata": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "urlPath": {
            "type": "string",
            "description": "Relative URL path based on the account URL ID."
          },
          "mimeType": { "type": "string" },
          "size": {
            "type": "number",
            "minimum": 0,
            "description": "Size in MiB (bytes divided by 1,048,576). UploadLinks.size uses bytes."
          },
          "uploadedAt": { "type": "string", "format": "date-time" },
          "visibility": { "type": "string", "enum": ["PUBLIC", "PRIVATE"] },
          "views": { "type": "integer", "minimum": 0 },
          "downloads": { "type": "integer", "minimum": 0 },
          "folderId": { "type": ["string", "null"] },
          "user": {
            "type": "object",
            "properties": { "urlId": { "type": "string" } },
            "required": ["urlId"]
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": { "type": "string" },
                "name": { "type": "string" }
              },
              "required": ["id", "name"]
            }
          },
          "hasPassword": { "type": "boolean" },
          "expiresAt": { "type": ["string", "null"], "format": "date-time" }
        },
        "required": [
          "id",
          "name",
          "urlPath",
          "mimeType",
          "size",
          "uploadedAt",
          "visibility",
          "views",
          "downloads",
          "folderId",
          "user",
          "tags",
          "hasPassword",
          "expiresAt"
        ],
        "description": "Owner-scoped metadata, including private files. Password hashes, content, and OCR text are not returned.",
        "example": {
          "id": "cm_example",
          "name": "screenshot.png",
          "urlPath": "/abc123/screenshot.png",
          "mimeType": "image/png",
          "size": 0.1953125,
          "uploadedAt": "2026-09-20T12:00:00.000Z",
          "visibility": "PUBLIC",
          "views": 0,
          "downloads": 0,
          "folderId": null,
          "user": { "urlId": "abc123" },
          "tags": [],
          "hasPassword": false,
          "expiresAt": null
        }
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "total": { "type": "integer", "minimum": 0 },
          "pageCount": { "type": "integer", "minimum": 0 },
          "page": { "type": "integer", "minimum": 1 },
          "limit": { "type": "integer", "minimum": 1, "maximum": 100 },
          "offset": {
            "type": "integer",
            "minimum": 0,
            "description": "Present for gallery-anchor navigation."
          }
        },
        "required": ["total", "pageCount", "page", "limit"]
      },
      "FileListResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "const": true },
          "data": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/FileMetadata" }
          },
          "pagination": { "$ref": "#/components/schemas/Pagination" }
        },
        "required": ["success", "data", "pagination"]
      },
      "FileTypesResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "types": { "type": "array", "items": { "type": "string" } }
            },
            "required": ["types"]
          },
          "success": { "const": true, "type": "boolean" }
        },
        "required": ["data", "success"]
      },
      "ShortLink": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "shortCode": {
            "type": "string",
            "minLength": 6,
            "maxLength": 6,
            "description": "The public link is instance-origin + /u/ + shortCode."
          },
          "targetUrl": { "type": "string", "format": "uri" },
          "clicks": { "type": "integer", "minimum": 0 },
          "createdAt": { "type": "string", "format": "date-time" },
          "userId": { "type": "string" }
        },
        "required": [
          "id",
          "shortCode",
          "targetUrl",
          "clicks",
          "createdAt",
          "userId"
        ],
        "example": {
          "id": "cm_link",
          "shortCode": "aB3_dE",
          "targetUrl": "https://example.com/a/long/path",
          "clicks": 0,
          "createdAt": "2026-09-20T12:00:00.000Z",
          "userId": "cm_account"
        }
      },
      "CreateShortLinkRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "pattern": "^https?://",
            "description": "Absolute HTTP or HTTPS destination."
          }
        },
        "required": ["url"]
      },
      "ShortLinkListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "urls": {
                "type": "array",
                "items": { "$ref": "#/components/schemas/ShortLink" }
              }
            },
            "required": ["urls"]
          },
          "success": { "const": true, "type": "boolean" }
        },
        "required": ["data", "success"]
      },
      "FileReadyEvent": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "title": "Flare file.ready event v1",
        "type": "object",
        "additionalProperties": false,
        "required": ["version", "id", "type", "occurredAt", "data"],
        "properties": {
          "version": { "const": 1 },
          "id": { "type": "string", "pattern": "^(file\\.ready:|test:).+" },
          "type": { "const": "file.ready" },
          "occurredAt": { "type": "string", "format": "date-time" },
          "test": { "const": true },
          "data": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "mimeType",
              "sizeBytes",
              "visibility",
              "passwordProtected"
            ],
            "properties": {
              "id": { "type": "string", "minLength": 1 },
              "name": { "type": "string" },
              "mimeType": { "type": "string" },
              "sizeBytes": { "type": "integer", "minimum": 0 },
              "visibility": { "enum": ["PUBLIC", "PRIVATE"] },
              "passwordProtected": { "type": "boolean" }
            }
          }
        }
      },
      "FileTimelineBucket": {
        "type": "object",
        "required": ["key", "from", "to", "count", "offset"],
        "properties": {
          "key": {
            "type": "string",
            "description": "Bucket start ISO instant, or all for a non-date sort."
          },
          "from": {
            "type": ["string", "null"],
            "format": "date-time",
            "description": "Inclusive calendar start in UTC; null for an undated bucket."
          },
          "to": {
            "type": ["string", "null"],
            "format": "date-time",
            "description": "Exclusive calendar end in UTC; subtract 1 ms for the inclusive GET /api/files dateTo parameter. Null for an undated bucket."
          },
          "count": { "type": "integer", "minimum": 1 },
          "offset": {
            "type": "integer",
            "minimum": 0,
            "description": "Number of matching files preceding this bucket in the requested order."
          }
        }
      },
      "FileTimelineResponse": {
        "type": "object",
        "required": ["success", "data"],
        "properties": {
          "success": { "type": "boolean", "enum": [true] },
          "data": {
            "type": "object",
            "required": ["total", "snapshot", "groupBy", "timezone", "buckets"],
            "properties": {
              "total": { "type": "integer", "minimum": 0 },
              "snapshot": {
                "type": "string",
                "format": "date-time",
                "description": "Inclusive upload-time ceiling for subsequent file windows; not a durable database snapshot."
              },
              "groupBy": {
                "type": "string",
                "enum": ["none", "month", "week", "year"]
              },
              "timezone": { "type": "string" },
              "buckets": {
                "type": "array",
                "items": { "$ref": "#/components/schemas/FileTimelineBucket" }
              }
            }
          }
        }
      }
    }
  },
  "x-flare-browser-session-api": {
    "handbook": "api/activity",
    "authentication": "Interactive browser session only; no named API token or legacy upload credential.",
    "operations": [
      {
        "method": "GET",
        "path": "/api/profile/sessions",
        "access": "Own account; independent of profile.update."
      },
      {
        "method": "DELETE",
        "path": "/api/profile/sessions",
        "access": "Own account; revokes all browser sessions including current; JSON response."
      },
      {
        "method": "DELETE",
        "path": "/api/profile/sessions/{id}",
        "access": "Own session only; JSON response reports whether current session ended."
      },
      {
        "method": "GET",
        "path": "/api/profile/login-history",
        "access": "Own account; outcome and cursor query filters."
      },
      {
        "method": "GET",
        "path": "/api/audit",
        "access": "audit.read permission; instance-wide filtered history."
      }
    ]
  }
}
