{
  "components": {
    "schemas": {
      "ApiToken": {
        "description": "One API token. The token's own secret is never included — it is shown once, at creation.",
        "properties": {
          "created_at": {
            "description": "Epoch seconds.",
            "format": "int64",
            "type": "integer"
          },
          "expires_at": {
            "description": "Epoch seconds; absent for a token that does not expire.",
            "format": "int64",
            "type": "integer"
          },
          "id": {
            "type": "string"
          },
          "last_used_at": {
            "description": "Epoch seconds; absent until the token has been used.",
            "format": "int64",
            "type": "integer"
          },
          "last_used_ip": {
            "description": "Absent until the token has been used.",
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "role": {
            "description": "`admin` can do anything a session can except manage tokens or sessions; `guest` is read-only.",
            "enum": [
              "admin",
              "guest"
            ],
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "role"
        ],
        "type": "object"
      },
      "ArchiveId": {
        "description": "Unique identifier for an archive (40-char SHA1)",
        "example": "d07ff399dddb25a0408385bc75b9ba52709fe2c1",
        "maxLength": 40,
        "minLength": 40,
        "pattern": "^[0-9a-f]{40}$",
        "type": "string"
      },
      "ArchiveMetadataJson": {
        "description": "JSON object for archive metadata for most API responses",
        "example": {
          "arcid": "28697b96f0ac5858be2614ed10ca47742c9522fd",
          "extension": "rar",
          "filename": "Fate GO MEMO",
          "isnew": false,
          "lastreadtime": 1337038281,
          "pagecount": 34,
          "progress": 3,
          "size": 1234567,
          "summary": null,
          "tags": "parody:fate grand order,  group:wadamemo,  artist:wada rco,  artbook,  full color",
          "title": "Fate GO MEMO",
          "toc": [
            {
              "name": "Chapter 1",
              "page": 1
            },
            {
              "name": "Chapter 2",
              "page": 13
            },
            {
              "name": "Chapter 3",
              "page": 25
            }
          ]
        },
        "properties": {
          "arcid": {
            "description": "Unique identifier for the archive (40-char SHA1) or tankoubon (15-char TANK_* ID)",
            "oneOf": [
              {
                "$ref": "#/components/schemas/ArchiveId"
              },
              {
                "$ref": "#/components/schemas/TankoubonId"
              }
            ],
            "type": "string"
          },
          "extension": {
            "description": "File extension of the archive",
            "type": "string"
          },
          "filename": {
            "description": "Filename of the archive",
            "type": "string"
          },
          "isnew": {
            "description": "Whether the archive is newly added.",
            "oneOf": [
              {
                "type": "boolean"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "lastreadtime": {
            "description": "Unix timestamp of when the archive was last read",
            "type": "integer"
          },
          "pagecount": {
            "description": "Total number of pages in the archive",
            "type": "integer"
          },
          "progress": {
            "description": "Reading progress (page number)",
            "type": "integer"
          },
          "size": {
            "description": "Size of the archive in bytes",
            "type": "integer"
          },
          "summary": {
            "description": "Summary description of the archive",
            "type": [
              "string",
              "null"
            ]
          },
          "tags": {
            "description": "Comma-separated list of tags associated with the archive",
            "type": "string"
          },
          "title": {
            "description": "Title of the archive",
            "type": "string"
          },
          "toc": {
            "description": "Table of contents for the archive",
            "type": "array"
          }
        },
        "required": [
          "arcid",
          "title",
          "filename",
          "tags",
          "isnew",
          "extension",
          "progress",
          "pagecount",
          "lastreadtime",
          "size"
        ],
        "type": "object"
      },
      "BackupArchiveMetadataJson": {
        "description": "JSON object for archive metadata in as returned in a backup",
        "properties": {
          "arcid": {
            "$ref": "#/components/schemas/ArchiveId",
            "description": "Unique identifier for the archive"
          },
          "filename": {
            "description": "Filename of the archive",
            "type": "string"
          },
          "summary": {
            "description": "Summary description of the archive",
            "type": [
              "string",
              "null"
            ]
          },
          "tags": {
            "description": "Comma-separated list of tags associated with the archive",
            "type": "string"
          },
          "thumbhash": {
            "description": "Thumbnail hash, or null if not set",
            "maxLength": 40,
            "minLength": 40,
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "description": "Title of the archive",
            "type": "string"
          }
        },
        "required": [
          "arcid",
          "title",
          "tags",
          "summary",
          "filename"
        ],
        "type": "object"
      },
      "BackupCategoryMetadataJson": {
        "description": "JSON object for category metadata in as returned in a backup",
        "properties": {
          "archives": {
            "description": "Array of archive IDs",
            "items": {
              "$ref": "#/components/schemas/ArchiveId"
            },
            "type": "array"
          },
          "catid": {
            "$ref": "#/components/schemas/CategoryId",
            "description": "ID of the category"
          },
          "name": {
            "description": "Name of the category",
            "type": "string"
          },
          "search": {
            "description": "Category search filter",
            "type": "string"
          }
        },
        "required": [
          "catid"
        ],
        "type": "object"
      },
      "CategoryId": {
        "description": "Unique identifier for a category (14-char SET_* ID)",
        "example": "SET_1613080290",
        "maxLength": 14,
        "minLength": 14,
        "pattern": "^SET_[0-9]{10}$",
        "type": "string"
      },
      "CategoryMetadataJson": {
        "description": "Category metadata.",
        "example": {
          "archives": [
            "0d50858d727723d856d2ab78564bb8e906e65f14",
            "7f03b8b1f337e1e42b2c2890533c0de7479d41ca"
          ],
          "id": "SET_1613080290",
          "name": "My great category",
          "pinned": 0,
          "search": null
        },
        "properties": {
          "archives": {
            "description": "Array of archive IDs (if this is a static category, empty otherwise)",
            "items": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/ArchiveId"
                },
                {
                  "$ref": "#/components/schemas/TankoubonId"
                }
              ],
              "type": "string"
            },
            "type": "array"
          },
          "id": {
            "$ref": "#/components/schemas/CategoryId",
            "description": "ID of the category"
          },
          "name": {
            "description": "Name of the category",
            "type": "string"
          },
          "pinned": {
            "description": "Whether this category should be pinned in the UI",
            "enum": [
              0,
              1
            ],
            "type": [
              "integer",
              "string"
            ]
          },
          "search": {
            "description": "Category search filter (if this is a dynamic category, empty otherwise)",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      },
      "JobStatus": {
        "description": "One tracked background job.",
        "properties": {
          "downloaded_bytes": {
            "description": "Bytes transferred so far, for a real download job. Absent — not `0` — until transfer has actually started, and for every non-download job.",
            "format": "int64",
            "type": "integer"
          },
          "error": {
            "description": "Failure message. Present only for a `failed` job.",
            "type": "string"
          },
          "finished_at": {
            "description": "Epoch milliseconds. Absent while the job is still in flight.",
            "format": "int64",
            "type": "integer"
          },
          "id": {
            "description": "Stable identifier for this job, used to look it up or clear it.",
            "type": "string"
          },
          "name": {
            "description": "Job type — a maintenance job name (`regen_thumbs`, a rescan, a duplicate scan, an index rebuild) or a download plugin's namespace for a download job.",
            "type": "string"
          },
          "progress": {
            "description": "Best-effort 0.0–1.0. Jobs that cannot report granular progress jump straight from 0 to 1 rather than reporting a fake intermediate value.",
            "type": "number"
          },
          "rate_limit_bytes_per_sec": {
            "description": "The rate limit actually in effect for this download, in bytes/second. Absent means unlimited — either no rule matched or the download has not started. There is no `0`/`null` sentinel, precisely because that would be ambiguous with \"unlimited\".",
            "format": "int64",
            "type": "integer"
          },
          "rate_limit_matched_pattern": {
            "description": "Which rule produced `rate_limit_bytes_per_sec`: an exact hostname, a wildcard such as `*.example.com`, or `*` for the general fallback.",
            "type": "string"
          },
          "result": {
            "description": "Job-specific result payload; its shape depends on `name`."
          },
          "result_truncated": {
            "description": "True when `result` was too large to keep in full.",
            "type": "boolean"
          },
          "started_at": {
            "description": "Epoch milliseconds. Absent until the job leaves `queued`.",
            "format": "int64",
            "type": "integer"
          },
          "state": {
            "enum": [
              "queued",
              "active",
              "finished",
              "failed"
            ],
            "type": "string"
          },
          "total_bytes": {
            "description": "Expected total size, from the response's `Content-Length`. Absent when the server does not report one: render an indeterminate indicator rather than treating absence as zero. For a multi-resource download this is the sum across all resources.",
            "format": "int64",
            "type": "integer"
          }
        },
        "required": [
          "id",
          "name",
          "state",
          "progress",
          "result_truncated"
        ],
        "type": "object"
      },
      "MinionJobResponse": {
        "description": "JSON object for a queued Minion job.",
        "properties": {
          "job": {
            "description": "Minion job ID",
            "type": "integer"
          },
          "operation": {
            "description": "Name of operation",
            "type": "string"
          },
          "success": {
            "description": "Returns 1 if operation was successful, else 0",
            "enum": [
              1,
              0
            ],
            "type": "integer"
          }
        },
        "required": [
          "job",
          "operation",
          "success"
        ],
        "type": "object"
      },
      "OperationResponse": {
        "properties": {
          "error": {
            "description": "Error message if any",
            "type": "string"
          },
          "operation": {
            "description": "Name of operation",
            "type": "string"
          },
          "success": {
            "description": "Returns 1 if operation was successful, else 0",
            "enum": [
              0,
              1
            ],
            "type": "integer"
          },
          "successMessage": {
            "description": "Success message if any",
            "type": "string"
          }
        },
        "required": [
          "operation",
          "success"
        ],
        "type": "object"
      },
      "PluginInfo": {
        "description": "Metadata payload for a LRR Plugin.",
        "example": {
          "author": "Difegue",
          "description": "Searches chaika.moe for tags matching your archive.",
          "icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA\\nB3RJTUUH4wYCFQocjU4r+QAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUH\\nAAAEZElEQVQ4y42T3WtTdxzGn/M7J+fk5SRpTk7TxMZkXU84tTbVNrUT3YxO7HA4pdtQZDe7cgx2\\ns8vBRvEPsOwFYTDYGJUpbDI2wV04cGXCGFLonIu1L2ptmtrmxeb1JDkvv121ZKVze66f74eH7/f5\\nMmjRwMCAwrt4/9KDpflMJpPHvyiR2DPcJklJ3TRDDa0xk36cvrm8vDwHAAwAqKrqjjwXecPG205w\\nHBuqa9rk77/d/qJYLD7cCht5deQIIczbgiAEKLVAKXWUiqVV06Tf35q8dYVJJBJem2A7Kwi2nQzD\\nZig1CG93+PO5/KN6tf5NKpVqbsBUVVVFUUxwHJc1TXNBoxojS7IbhrnLMMx9pVJlBqFQKBKPxwcB\\nkJYgjKIo3QCE1nSKoghbfJuKRqN2RVXexMaQzWaLezyeEUEQDjscjk78PxFFUYRkMsltJgGA3t7e\\nyMLCwie6rr8iCILVbDbvMgwzYRjGxe0o4XC4s1AoHPP5fMP5/NNOyzLKAO6Ew+HrDADBbre/Ryk9\\nnzx81FXJNlEpVpF+OqtpWu2MpmnXWmH9/f2umZmZi4cOHXnLbILLzOchhz1YerJAs9m1GwRAg2GY\\nh7GYah488BJYzYW+2BD61AFBlmX/1nSNRqN9//792ujoaIPVRMjOKHoie3DytVGmp2fXCAEAjuMm\\nu7u7Umosho6gjL/u/QHeEgvJZHJ2K/D+/fuL4+PjXyvPd5ldkShy1UXcmb4DnjgQj/fd5gDA6/XS\\nYCAwTwh9oT3QzrS1+VDVi+vd3Tsy26yQVoFF3dAXJVmK96p9EJ0iLNOwKKU3CQCk0+lSOpP5WLDz\\nF9Q9kZqyO0SloOs6gMfbHSU5NLRiUOuax2/HyZPHEOsLw2SbP83eu/fLxrkNp9P554XxCzVa16MC\\n7+BPnTk9cfmH74KJE8nmga7Xy5JkZ8VKifGIHpoBb1VX8hNTd3/t/7lQ3OeXfFPvf/jBRw8ezD/a\\n7M/aWq91cGgnJaZ2VcgSdnV1XRNNd3vAoBVVYusmnEQS65hfgSG6c+zy3Kre7nF/KrukcMW0Zg8O\\nD08DoJutDxxOEb5IPUymwrq8ft1gLKfkFojkkRxemERCAQUACPFWRazYLJcrFGwQhyufbQQ7rFpy\\nLMkCwGZC34qPIuwp+XPOjBFwazQ/txrdFS2GGS/Xuj+pUKLGk1Kjvlded3s72lyGW+PLbGVcmrAA\\ngN0wTk1NWYODg9XOKltGtpazi5GigzroUnHN5nUHG1ylRsG7rDXHmnEpu4CeEtEKkqNc6QqlLc/M\\n8uT5lLH5eq0aGxsju1O7GQB498a5s/0x9dRALPaQEDZnYwnhWJtMCCNrjeb0UP34Z6e/PW22zjPP\\n+vwXBwfPvbw38XnXjk7GsiwKAIQQhjAMMrlsam45d+zLH6/8o6vkWcBcrXbVKQhf6bpucCwLjmUB\\nSmmhXC419eblrbD/TAgAkUjE987xE0c7ZDmk66ajUCnq+cL63fErl25s5/8baQPaWLhx6goAAAAA\\nSUVORK5CYII=",
          "name": "Chaika.moe",
          "namespace": "trabant",
          "oneshot_arg": "Chaika Gallery or Archive URL (Will attach matching tags to your archive)",
          "origin": "builtin",
          "parameters": [
            {
              "desc": "ipb_pass_hash cookie",
              "type": "string"
            },
            {
              "default_value": 0,
              "desc": "Force resampled archive download",
              "name": "forceresampled",
              "type": "bool"
            }
          ],
          "type": "metadata",
          "version": "2.1"
        },
        "properties": {
          "author": {
            "description": "Author of the plugin",
            "type": "string"
          },
          "description": {
            "description": "Description of the plugin",
            "type": "string"
          },
          "icon": {
            "description": "Base64 image of the plugin icon",
            "type": "string"
          },
          "login_from": {
            "description": "Namespace of the login plugin this plugin depends on",
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "description": "Name of the plugin",
            "type": "string"
          },
          "namespace": {
            "description": "Unique namespace for the plugin",
            "type": "string"
          },
          "oneshot_arg": {
            "description": "Description of the manual one-shot argument users can fill in for manual plugin calls",
            "type": [
              "string",
              "null"
            ]
          },
          "origin": {
            "description": "Where the installed plugin came from.",
            "enum": [
              "builtin",
              "sideloaded",
              "managed"
            ],
            "type": "string"
          },
          "parameters": {
            "items": {
              "$ref": "#/components/schemas/PluginParameter"
            },
            "type": "array"
          },
          "registry": {
            "description": "Registry ID the plugin was installed from, or null if not registry-managed",
            "nullable": true,
            "type": "string"
          },
          "sha256": {
            "description": "SHA-256 recorded for the installed artifact, or null if not registry-managed",
            "nullable": true,
            "type": "string"
          },
          "type": {
            "description": "Type of the plugin",
            "enum": [
              "download",
              "login",
              "metadata",
              "script"
            ],
            "type": "string"
          },
          "url_regex": {
            "description": "For downloader plugins, regex where it should be used",
            "type": [
              "string",
              "null"
            ]
          },
          "version": {
            "description": "Version of the plugin",
            "type": "string"
          }
        },
        "type": "object"
      },
      "PluginParameter": {
        "description": "Parameter for a LRR Plugin.",
        "example": {
          "default_value": 0,
          "desc": "Force resampled archive download",
          "name": "forceresampled",
          "type": "bool"
        },
        "properties": {
          "default_value": {
            "description": "Default value of the parameter",
            "type": [
              "boolean",
              "integer",
              "string",
              "null"
            ]
          },
          "desc": {
            "description": "Description of the parameter",
            "type": "string"
          },
          "name": {
            "description": "Name of the parameter",
            "type": [
              "string",
              "null"
            ]
          },
          "type": {
            "description": "Type of the parameter. This isn't really checked server-side and is used to inform the config page for the plugin.",
            "enum": [
              "bool",
              "int",
              "string"
            ],
            "type": "string"
          }
        },
        "type": "object"
      },
      "RegistryMetadata": {
        "allOf": [
          {
            "if": {
              "properties": {
                "provider": {
                  "enum": [
                    "github",
                    "gitea"
                  ]
                }
              },
              "required": [
                "provider"
              ]
            },
            "then": {
              "properties": {
                "url": {
                  "pattern": "^https://"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "provider": {
                  "const": "github"
                }
              },
              "required": [
                "provider"
              ]
            },
            "then": {
              "properties": {
                "url": {
                  "pattern": "^https://github\\.com/[^/]+/[^/]+"
                }
              }
            }
          }
        ],
        "description": "Registry configuration with ID.",
        "properties": {
          "created": {
            "description": "Unix timestamp of registry creation.",
            "type": "integer"
          },
          "id": {
            "description": "Registry ID.",
            "maxLength": 14,
            "minLength": 14,
            "type": "string"
          },
          "name": {
            "description": "User-friendly alias for this registry.",
            "minLength": 1,
            "type": "string"
          },
          "path": {
            "description": "Local filesystem path to the registry. Present when provider is 'local'. Always absolute.",
            "minLength": 1,
            "pattern": "^(/|[A-Za-z]:[\\\\/]|\\\\\\\\)",
            "type": "string"
          },
          "provider": {
            "description": "Registry source provider.",
            "enum": [
              "github",
              "gitea",
              "cdn",
              "local"
            ],
            "type": "string"
          },
          "ref": {
            "description": "Git ref (branch, tag, or commit). Present for git providers.",
            "minLength": 1,
            "type": "string"
          },
          "updated": {
            "description": "Unix timestamp of last update.",
            "type": "integer"
          },
          "url": {
            "description": "Registry URL. HTTPS git repository URL for git providers (github, gitea),\nor http://-or-https:// CDN base URL for 'cdn'.\n",
            "format": "uri",
            "pattern": "^https?://",
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "provider",
          "created",
          "updated"
        ],
        "type": "object"
      },
      "ServerInfo": {
        "description": "Server info payload emitted by /api/info. Older server versions used integers instead of booleans here.",
        "example": {
          "archives_per_page": 100,
          "authenticated_progress": false,
          "cache_last_cleared": 1728852701,
          "debug_mode": true,
          "excluded_namespaces": [
            "source",
            "date_added"
          ],
          "has_password": false,
          "motd": "aye lads time to read some manga",
          "name": "LANraragi",
          "nofun_mode": false,
          "restart_required": false,
          "server_resizes_images": false,
          "server_tracks_progress": true,
          "total_archives": 104,
          "total_pages_read": 252,
          "version": "0.9.30",
          "version_desc": "I'm under Japanese influence and my honor's at stake!",
          "version_name": "Law (Earthlings On Fire)"
        },
        "properties": {
          "archives_per_page": {
            "description": "How many archives per page the server lists",
            "type": "integer"
          },
          "authenticated_progress": {
            "description": "Whether the instance requires authentication for server-side progress updates",
            "type": "boolean"
          },
          "cache_last_cleared": {
            "description": "Timestamp for last time the search cache was wiped",
            "type": "integer"
          },
          "debug_mode": {
            "description": "Whether the instance has debug mode enabled",
            "type": "boolean"
          },
          "excluded_namespaces": {
            "description": "List of tag namespaces excluded from search suggestions and tag statistics",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "has_password": {
            "description": "Whether the instance is password-protected",
            "type": "boolean"
          },
          "motd": {
            "description": "MOTD of the instance",
            "type": "string"
          },
          "name": {
            "description": "Name of the instance",
            "type": "string"
          },
          "nofun_mode": {
            "description": "Whether the instance has no-fun mode enabled",
            "type": "boolean"
          },
          "restart_required": {
            "description": "Whether LRR needs a restart",
            "type": "boolean"
          },
          "server_resizes_images": {
            "description": "Whether the instance auto-downsizes images before serving them",
            "type": "boolean"
          },
          "server_tracks_progress": {
            "description": "Whether the instance tracks reading progression server-side",
            "type": "boolean"
          },
          "total_archives": {
            "description": "Total amount of archives stored on instance",
            "type": "integer"
          },
          "total_pages_read": {
            "description": "Total amount of pages read",
            "type": "integer"
          },
          "version": {
            "description": "Version of the server",
            "type": "string"
          },
          "version_desc": {
            "description": "Version description",
            "type": "string"
          },
          "version_name": {
            "description": "Version name",
            "type": "string"
          }
        },
        "type": "object"
      },
      "Session": {
        "description": "One signed-in device, identified by its refresh-token family.",
        "properties": {
          "auto_name": {
            "description": "The device-derived name, always present, regardless of whether a custom name overrides it.",
            "type": "string"
          },
          "created_at": {
            "description": "Epoch seconds.",
            "format": "int64",
            "type": "integer"
          },
          "current": {
            "description": "True for the session making this request. Useful for not offering to revoke yourself by accident.",
            "type": "boolean"
          },
          "custom_name": {
            "description": "The user-assigned name, when one has been set.",
            "type": "string"
          },
          "expires_at": {
            "description": "Epoch seconds; the session's absolute expiry.",
            "format": "int64",
            "type": "integer"
          },
          "family_id": {
            "description": "Identifier used to rename or revoke this session.",
            "type": "string"
          },
          "idle_expires_at": {
            "description": "Epoch seconds; when an idle session stops being usable.",
            "format": "int64",
            "type": "integer"
          },
          "ip": {
            "description": "The address this session was last seen from.",
            "type": "string"
          },
          "last_seen_at": {
            "description": "Epoch seconds.",
            "format": "int64",
            "type": "integer"
          },
          "name": {
            "description": "The session's display name — `custom_name` if one was set, otherwise a name derived from the device.",
            "type": "string"
          }
        },
        "required": [
          "family_id",
          "name",
          "current"
        ],
        "type": "object"
      },
      "StampsData": {
        "description": "JSON object for the Stamp model",
        "example": {
          "content": "Lorem ipsum dolore",
          "position": "12,34"
        },
        "properties": {
          "content": {
            "description": "Text of the stamp",
            "type": "string"
          },
          "id": {
            "description": "ID of the stamp",
            "type": "string"
          },
          "position": {
            "description": "Position of the stamp in the page in normalized coordinates (0-100)",
            "type": "string"
          }
        },
        "required": [
          "position",
          "content"
        ],
        "type": "object"
      },
      "StampsResponse": {
        "properties": {
          "result": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "TankoubonBackupJson": {
        "description": "Tankoubon metadata as returned in a backup",
        "example": {
          "archives": [
            "e69e43e1355267f7d32a4f9b7f2fe108d2401ebf",
            "e69e43e1355267f7d32a4f9b7f2fe108d2401eba"
          ],
          "name": "sailor moon",
          "tankid": "TANK_1749925516"
        },
        "properties": {
          "archives": {
            "description": "Array of archive IDs",
            "items": {
              "$ref": "#/components/schemas/ArchiveId"
            },
            "type": "array"
          },
          "name": {
            "description": "Name of the tankoubon",
            "type": "string"
          },
          "summary": {
            "description": "Summary of the tankoubon",
            "type": "string"
          },
          "tags": {
            "description": "Tags associated with the tankoubon",
            "type": "string"
          },
          "tankid": {
            "$ref": "#/components/schemas/TankoubonId",
            "description": "ID of the tankoubon"
          }
        },
        "required": [
          "tankid"
        ],
        "type": "object"
      },
      "TankoubonId": {
        "description": "Unique identifier for a tankoubon (15-char TANK_* ID)",
        "example": "TANK_1749925516",
        "maxLength": 15,
        "minLength": 15,
        "pattern": "^TANK_[0-9]{10}$",
        "type": "string"
      },
      "TankoubonMetadataJson": {
        "description": "Tankoubon metadata as returned by the list endpoint",
        "example": {
          "archives": [
            "e69e43e1355267f7d32a4f9b7f2fe108d2401ebf",
            "e69e43e1355267f7d32a4f9b7f2fe108d2401eba"
          ],
          "id": "TANK_1749925516",
          "name": "sailor moon",
          "progress": 0,
          "summary": "",
          "tags": ""
        },
        "properties": {
          "archives": {
            "description": "Array of archive IDs",
            "items": {
              "$ref": "#/components/schemas/ArchiveId"
            },
            "type": "array"
          },
          "id": {
            "$ref": "#/components/schemas/TankoubonId",
            "description": "ID of the tankoubon"
          },
          "name": {
            "description": "Name of the tankoubon",
            "type": "string"
          },
          "progress": {
            "description": "Reading progress (page number) for this Tankoubon",
            "type": "integer"
          },
          "summary": {
            "description": "Summary of the tankoubon",
            "type": "string"
          },
          "tags": {
            "description": "Tags associated with the tankoubon",
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "description": "An API token created in your instance's settings. Send it as `Authorization: Bearer <token>`.",
        "scheme": "bearer",
        "type": "http"
      },
      "sessionCookie": {
        "description": "The session cookie set by `POST /api/login`. Only this site's own frontend uses it; there is no way to obtain one from an API token, which is why the endpoints below accept nothing else.",
        "in": "cookie",
        "name": "lanrurugi_session",
        "type": "apiKey"
      }
    }
  },
  "info": {
    "description": "The complete HTTP API of a LANrurugi instance.\n\nThis document is generated from the server's own route registrations, so every endpoint listed here is one the server actually serves, and every endpoint the server serves is listed. Endpoints that also exist in LANraragi carry that project's field-level documentation; the rest are listed with the handler's own notes and a pointer into the source.\n\nTo try a request, set the server address to your own instance (the default is the local port) and supply an API token from your instance's settings. Requests go from your browser straight to your instance.",
    "license": {
      "identifier": "MIT",
      "name": "MIT"
    },
    "title": "LANrurugi API",
    "version": "0.1.0"
  },
  "openapi": "3.1.0",
  "paths": {
    "/api/activity": {
      "delete": {
        "description": "Bulk delete by ID. Session-only — an API token of either role is refused, so a token cannot\nerase its own audit trail.\n",
        "operationId": "bulkDeleteActivity",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "ids": {
                    "items": {
                      "type": "string"
                    },
                    "type": "array"
                  }
                },
                "required": [
                  "ids"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/OperationResponse"
                    },
                    {
                      "properties": {
                        "deleted_count": {
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    }
                  ]
                }
              }
            },
            "description": "How many entries were removed."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "summary": "Delete selected activity entries",
        "tags": [
          "activity"
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/activity.rs:319"
      },
      "get": {
        "description": "A page of activity entries, newest first, scoped to what the caller is allowed to see (see\nthis file's header).\n\nEntries whose target is an archive are annotated with whether that archive still exists, so a\nclient can render \"deleted since\" instead of a dead link.\n\n\n<!-- lanrurugi:access-notes -->\n\n**Access differs by role**\n\n- Which entries you receive depends on who you are. A browser session sees every entry. An administrator token sees the entries it produced itself, plus every entry produced by a guest token. A guest token sees only the entries it produced itself. A token holder is therefore looking at a subset of the log, not at the log — an empty result does not mean nothing happened.",
        "operationId": "listActivity",
        "parameters": [
          {
            "description": "Opaque pagination cursor from a previous response's `next_cursor`.",
            "in": "query",
            "name": "cursor",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Clamped to a small fixed range regardless of what is asked for — an unbounded limit would let one request force an arbitrarily large scan.",
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Only entries at or after this epoch-second timestamp.",
            "in": "query",
            "name": "start_ts",
            "schema": {
              "format": "int64",
              "type": "integer"
            }
          },
          {
            "description": "Only entries at or before this epoch-second timestamp.",
            "in": "query",
            "name": "end_ts",
            "schema": {
              "format": "int64",
              "type": "integer"
            }
          },
          {
            "description": "Comma-separated actor keys; multiple values are OR'd together.",
            "in": "query",
            "name": "actor",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Comma-separated action types; multiple values are OR'd together.",
            "in": "query",
            "name": "action_type",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Comma-separated outcomes; multiple values are OR'd together.",
            "in": "query",
            "name": "outcome",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "entries": {
                      "items": {
                        "description": "One activity entry: `id`, `timestamp`, `actor`, `auto_or_manual`, `action_type`, `target` (with an added `exists` flag for archive targets), `outcome`, and optional `client_ip`, `device_info`, `device_name`, `before`, `after` and `caused_by`.",
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "next_cursor": {
                      "description": "Pass back as `cursor` for the next page. Absent on the last page.",
                      "type": "string"
                    },
                    "total_estimate": {
                      "description": "An estimate, not an exact count.",
                      "type": "integer"
                    }
                  },
                  "required": [
                    "entries"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "One page of entries."
          }
        },
        "summary": "List activity entries",
        "tags": [
          "activity"
        ],
        "x-lanrurugi-access-notes": [
          "Which entries you receive depends on who you are. A browser session sees every entry. An administrator token sees the entries it produced itself, plus every entry produced by a guest token. A guest token sees only the entries it produced itself. A token holder is therefore looking at a subset of the log, not at the log — an empty result does not mean nothing happened."
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/activity.rs:319"
      }
    },
    "/api/activity/facets": {
      "get": {
        "description": "The distinct action types and actors present in the log, each with a count — so a client can\nbuild a filter UI from what is actually there rather than from a hardcoded list.\n\nLike the list endpoint, this is scoped to what the caller may see: an actor only appears if\nthe caller could read that actor's entries.\n",
        "operationId": "getFacets",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "action_types": {
                      "items": {
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "actors": {
                      "items": {
                        "type": "object"
                      },
                      "type": "array"
                    }
                  },
                  "required": [
                    "action_types",
                    "actors"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "The available facets."
          }
        },
        "summary": "List activity filter facets",
        "tags": [
          "activity"
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/activity.rs:320"
      }
    },
    "/api/activity/retention": {
      "get": {
        "description": "How long activity entries are kept, in seconds.",
        "operationId": "getRetention",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "retention_secs": {
                      "format": "int64",
                      "type": "integer"
                    }
                  },
                  "required": [
                    "retention_secs"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "The current window."
          }
        },
        "summary": "Read the activity retention window",
        "tags": [
          "activity"
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/activity.rs:322"
      },
      "put": {
        "description": "Changes how long activity entries are kept. Session-only: a token must not be able to shorten\nhow long its own trail survives.\n",
        "operationId": "putRetention",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "retention_secs": {
                    "format": "int64",
                    "type": "integer"
                  }
                },
                "required": [
                  "retention_secs"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Stored."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "summary": "Set the activity retention window",
        "tags": [
          "activity"
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/activity.rs:322"
      }
    },
    "/api/activity/{id}": {
      "delete": {
        "description": "Deletes a single entry by ID. Session-only, for the same reason the bulk delete is.\n",
        "operationId": "deleteActivity",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Deleted."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "No entry with that ID."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "summary": "Delete one activity entry",
        "tags": [
          "activity"
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/activity.rs:321"
      }
    },
    "/api/archives": {
      "delete": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/archives.rs:616), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`DELETE /archives` (issue #63), body `{ids: [...]}` — same \"plural resource, `DELETE` with a\nJSON body carrying which ones\" shape `activity.rs::bulk_delete_activity` (`DELETE /activity`)\nalready established in this codebase, not a `POST .../batch-delete` action-style path; `DELETE`\n*can* carry a request body per RFC 9110 (silently dropped by some older proxies/clients in\npractice, but Axum and this project's own frontend both handle it fine, and precedent already\nexists here). The Batch page's own \"delete archives\" operation previously fired\n`DELETE /archives/{id}` once per selected archive from the frontend as a plain sequential loop\n(no atomicity, no single audit point, and — since each request only ever reported its own\npass/fail — no way for the caller to know *which* ids failed without diffing the library\nbefore/after). This does the same per-id work (`delete_one_archive`, identical cleanup to the\nsingle-delete endpoint) server-side in one request, and reports a per-id result list so the\nfrontend can show exactly what happened rather than assuming full success.\n\nSession-only (`route_policy.csv` denies every token role and `anonymous`) — deliberately\nstricter than every other archive-mutating endpoint in this file, all of which a Token (Admin\nor Guest) can call. A single wrong/leaked API token being able to wipe an arbitrary-sized slice\nof the library in one request is a materially larger blast radius than any one of the\nsingle-target endpoints token access already permits, and this project has no legitimate\nautomation use case for bulk deletion the way it does for e.g. scripted uploads or metadata\nedits — so the extra restriction has no real workflow it breaks.",
        "operationId": "batchDeleteArchives",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/archives.rs:207."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "summary": "`DELETE /archives` (issue #63), body `{ids: [...]}` — same \"plural resource, `DELETE` with a",
        "tags": [
          "archives"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archives.rs:207",
        "x-lanrurugi-undocumented": true
      },
      "get": {
        "description": "Returns a list of all Archives in the database. This doesn't include Tankoubons by design.  You can use the IDs of this JSON with the other endpoints.\n\n\n<!-- lanrurugi:access-notes -->\n\n**Access differs by role**\n\n- Not reachable by a guest visitor, or anonymously, at all. This is the unscoped listing — it returns every archive in the library and applies no category scoping — so granting it to guests would defeat the guest-visible restriction that `/api/search` enforces. Guests browse through `/api/search` instead.",
        "operationId": "getAllArchives",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/ArchiveMetadataJson"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Array of archives"
          }
        },
        "summary": "Get all Archives",
        "tags": [
          "archives"
        ],
        "x-lanrurugi-access-notes": [
          "Not reachable by a guest visitor, or anonymously, at all. This is the unscoped listing — it returns every archive in the library and applies no category scoping — so granting it to guests would defeat the guest-visible restriction that `/api/search` enforces. Guests browse through `/api/search` instead."
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archives.rs:207"
      }
    },
    "/api/archives/untagged": {
      "get": {
        "description": "Get Archives that don't have any tags recorded.  This follows the same rules as the Batch Tagging filter and will include Archives that have parody:, date_added:, series: or artist: tags.\n",
        "operationId": "getUntaggedArchives",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": [
                  "d1858d5dc36925aa66be072a97817650d39de166",
                  "c3458d5dc36925da93be072a97817650d39de166",
                  "28697b96f0ac5858be2614ed10ca47742c9522fd"
                ],
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/ArchiveId"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Array of archive IDs"
          }
        },
        "summary": "Get all untagged archives",
        "tags": [
          "archives"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archives.rs:211"
      }
    },
    "/api/archives/upload": {
      "put": {
        "description": "Upload an Archive to the server.  \nIf a SHA1 checksum of the Archive is included, the server will perform an optional in-transit, file integrity validation, and reject the upload if the server-side checksum does not match.",
        "operationId": "uploadArchive",
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "properties": {
                  "category_id": {
                    "$ref": "#/components/schemas/CategoryId",
                    "description": "Category ID you'd want the archive to be added to."
                  },
                  "file": {
                    "description": "Archive file to upload",
                    "format": "binary",
                    "type": "string"
                  },
                  "file_checksum": {
                    "description": "SHA1 checksum of the archive for in-transit validation.",
                    "pattern": "^[a-fA-F0-9]{40}$",
                    "type": "string"
                  },
                  "summary": {
                    "description": "Summary of the Archive.",
                    "type": "string"
                  },
                  "tags": {
                    "description": "Set of tags you want to insert in the database alongside the archive.",
                    "type": "string"
                  },
                  "title": {
                    "description": "Title of the Archive.",
                    "type": "string"
                  }
                },
                "required": [
                  "file"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "id": {
                      "$ref": "#/components/schemas/ArchiveId",
                      "description": "ID of the uploaded archive"
                    },
                    "operation": {
                      "description": "Name of operation",
                      "example": "upload",
                      "type": "string"
                    },
                    "success": {
                      "description": "Returns 1 if operation was successful, else 0",
                      "enum": [
                        1
                      ],
                      "type": "integer"
                    }
                  },
                  "required": [
                    "operation",
                    "success",
                    "id"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Archive uploaded successfully"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Bad request"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "error": "This file already exists in the Library. Enable replace duplicated archive in config to replace old ones.",
                    "id": "0b91b546850e881034833c73375558928ddcec7c",
                    "operation": "upload",
                    "success": 0
                  },
                  "properties": {
                    "error": {
                      "description": "Error message indicating the archive already exists",
                      "type": "string"
                    },
                    "id": {
                      "$ref": "#/components/schemas/ArchiveId",
                      "description": "ID of the existing archive"
                    },
                    "operation": {
                      "description": "Name of operation",
                      "enum": [
                        "upload"
                      ],
                      "type": "string"
                    },
                    "success": {
                      "description": "Returns 0 for failed operation",
                      "enum": [
                        0
                      ],
                      "type": "integer"
                    }
                  },
                  "required": [
                    "operation",
                    "success",
                    "error",
                    "id"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Duplicate archive"
          },
          "415": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Unsupported file"
          },
          "417": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Checksum mismatch"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Unprocessable Entity"
          },
          "423": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Locked resource response"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Internal Server Error"
          }
        },
        "summary": "Upload Archive",
        "tags": [
          "archives"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/upload.rs:45",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/archives/{id}": {
      "delete": {
        "description": "Delete both the archive metadata and the file stored on the server.  \n🙏 Please ask your user for confirmation before invoking this endpoint.",
        "operationId": "deleteArchive",
        "parameters": [
          {
            "description": "ID of the Archive to process.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/ArchiveId"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "filename": "big_chungus.zip",
                    "id": "75d18ce470dc99f83dc355bdad66319d1f33c82b",
                    "operation": "delete_archive",
                    "success": 1
                  },
                  "properties": {
                    "filename": {
                      "description": "Filename of the deleted archive",
                      "type": "string"
                    },
                    "id": {
                      "$ref": "#/components/schemas/ArchiveId",
                      "description": "ID of the archive"
                    },
                    "operation": {
                      "description": "Name of the operation",
                      "enum": [
                        "delete_archive"
                      ],
                      "type": "string"
                    },
                    "success": {
                      "description": "Returns 1 if operation was successful, else 0",
                      "enum": [
                        0,
                        1
                      ],
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Archive deleted response"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "No archive ID response"
          },
          "423": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Locked resource response"
          }
        },
        "summary": "Delete archive",
        "tags": [
          "archives"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archives.rs:219",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/archives/{id}/bookmarks": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/bookmarks.rs:479), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`GET /archives/{id}/bookmarks` — unpaginated, just this one archive's bookmarked pages\n(ascending, each with its resolved filename). Used by the reader (is the current page\nbookmarked? — only `page` matters there) and `BookmarkHoverGrid` (needs both `page` and\n`filename` for its caption, previously a second `GET /archives/{id}/files` call). Doesn't\nrequire the archive to exist — same posture as `remove_bookmark`, a nonexistent archive simply\nhas no bookmarks to report (and no filenames to resolve).\n\n<!-- lanrurugi:access-notes -->\n\n**Access differs by role**\n\n- For a guest visitor whose scope excludes this archive, this answers **404**, not 403 — deliberately indistinguishable from an archive that does not exist, so the response cannot be used to probe for archives outside the guest's scope.",
        "operationId": "listBookmarksForArchive",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/bookmarks.rs:36."
          }
        },
        "summary": "`GET /archives/{id}/bookmarks` — unpaginated, just this one archive's bookmarked pages",
        "tags": [
          "bookmarks"
        ],
        "x-lanrurugi-access-notes": [
          "For a guest visitor whose scope excludes this archive, this answers **404**, not 403 — deliberately indistinguishable from an archive that does not exist, so the response cannot be used to probe for archives outside the guest's scope."
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/bookmarks.rs:36",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/archives/{id}/bookmarks/{page}": {
      "delete": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/bookmarks.rs:751), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`DELETE /archives/{id}/bookmarks/{page}` — idempotent, same as\n`BookmarksRepository::remove`'s own contract; doesn't check archive existence first since\nremoving a bookmark for an already-deleted archive is a legitimate cleanup, not an error. The\nactivity-log lookup below is a *best-effort* label fetch only, separate from that leniency — a\nmissing archive still lets the removal itself proceed, just with `target.label: None` (falls\nback to `target.id` alone, same as any other action type whose resource vanished first).",
        "operationId": "removeBookmark",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/bookmarks.rs:37."
          }
        },
        "summary": "`DELETE /archives/{id}/bookmarks/{page}` — idempotent, same as",
        "tags": [
          "bookmarks"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/bookmarks.rs:37",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      },
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/bookmarks.rs:689), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`POST /archives/{id}/bookmarks/{page}` — bookmarks does not validate `page` against the\narchive's real `pagecount`, matching `update_progress`'s own leniency (`archives.rs`) — the\nreader is the only caller and always passes its own current, already-valid page.",
        "operationId": "addBookmark",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/bookmarks.rs:37."
          }
        },
        "summary": "`POST /archives/{id}/bookmarks/{page}` — bookmarks does not validate `page` against the",
        "tags": [
          "bookmarks"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/bookmarks.rs:37",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/archives/{id}/bookmarks/{page}/name": {
      "put": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/bookmarks.rs:562), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`PUT /archives/{id}/bookmarks/{page}/name` — sets or clears this bookmark's own name.\n`404`s if the bookmark itself doesn't exist yet (a name can't outlive the bookmark it names,\nand setting one on a nonexistent bookmark would silently create an orphaned `NAMES_HASH_KEY`\nentry no `add_bookmark` call ever cleans up) — matches `add_stamp`'s own \"archive must exist\nfirst\" posture, just checking bookmark existence instead of archive existence.",
        "operationId": "renameBookmark",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/bookmarks.rs:47."
          }
        },
        "summary": "`PUT /archives/{id}/bookmarks/{page}/name` — sets or clears this bookmark's own name",
        "tags": [
          "bookmarks"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/bookmarks.rs:47",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/archives/{id}/categories": {
      "get": {
        "description": "Get all the Categories which currently refer to this Archive ID.",
        "operationId": "getArchiveCategories",
        "parameters": [
          {
            "description": "ID of the Archive to process.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/ArchiveId"
                },
                {
                  "$ref": "#/components/schemas/TankoubonId"
                }
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "categories": [
                      {
                        "archives": [
                          "0d50858d727723d856d2ab78564bb8e906e65f14",
                          "7f03b8b1f337e1e42b2c2890533c0de7479d41ca"
                        ],
                        "id": "SET_1613080290",
                        "name": "My great category",
                        "pinned": 0,
                        "search": null
                      },
                      {
                        "archives": [
                          "0d50858d727723d856d2ab78564bb8e906e65f14",
                          "75d18ce470dc99f83dc355bdad66319d1f33c82b"
                        ],
                        "id": "SET_1613080294",
                        "name": "My other category",
                        "pinned": 1,
                        "search": null
                      }
                    ],
                    "operation": "find_arc_categories",
                    "success": 1
                  },
                  "properties": {
                    "categories": {
                      "description": "Array of category metadata",
                      "items": {
                        "$ref": "#/components/schemas/CategoryMetadataJson"
                      },
                      "type": "array"
                    },
                    "operation": {
                      "description": "Name of the operation",
                      "enum": [
                        "find_arc_categories"
                      ],
                      "type": "string"
                    },
                    "success": {
                      "description": "Returns 1 if operation was successful, else 0",
                      "enum": [
                        0,
                        1
                      ],
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Archive categories"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "No archive ID response"
          }
        },
        "summary": "Get archive categories",
        "tags": [
          "archives"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archives.rs:239"
      }
    },
    "/api/archives/{id}/download": {
      "get": {
        "description": "Download an Archive from the server.",
        "operationId": "downloadArchive",
        "parameters": [
          {
            "description": "ID of the Archive to download.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/ArchiveId"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/octet-stream": {
                "schema": {
                  "format": "binary",
                  "type": "string"
                }
              }
            },
            "description": "Archive file"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "No archive ID specified.",
                  "operation": "download_archive",
                  "success": 0
                },
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "No archive ID response"
          }
        },
        "summary": "Download archive",
        "tags": [
          "archives"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archives.rs:245"
      }
    },
    "/api/archives/{id}/files": {
      "get": {
        "description": "Get a list of URLs pointing to the images contained in an archive.  If necessary, this endpoint also launches a background Minion job to extract the archive so it is ready for reading.\n\n<!-- lanrurugi:access-notes -->\n\n**Access differs by role**\n\n- For a guest visitor whose scope excludes this archive, this answers **404**, not 403 — deliberately indistinguishable from an archive that does not exist, so the response cannot be used to probe for archives outside the guest's scope.",
        "operationId": "getFiles",
        "parameters": [
          {
            "description": "ID of the Archive to process.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/ArchiveId"
            }
          },
          {
            "description": "Force a full background re-extraction of the Archive. Existing cached files might still be used in subsequent `/api/archives/:id/page` calls until the Archive is fully re-extracted.",
            "in": "query",
            "name": "force",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "job": 561,
                  "pages": [
                    ".\\/api\\/archives\\/28697b96f0ac5858be2614ed10ca47742c9522fd\\/page&path=00.jpg",
                    ".\\/api\\/archives\\/28697b96f0ac5858be2614ed10ca47742c9522fd\\/page&path=01.jpg"
                  ]
                },
                "schema": {
                  "properties": {
                    "job": {
                      "description": "ID of the background extract job",
                      "type": "integer"
                    },
                    "pages": {
                      "items": {
                        "description": "Page URLs",
                        "type": "string"
                      },
                      "type": "array"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "You get page URLs, and the ID of the background extract job."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "No archive ID specified.",
                  "operation": "get_file_list",
                  "success": 0
                },
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "No archive ID response"
          }
        },
        "summary": "Extract an Archive",
        "tags": [
          "archives"
        ],
        "x-lanrurugi-access-notes": [
          "For a guest visitor whose scope excludes this archive, this answers **404**, not 403 — deliberately indistinguishable from an archive that does not exist, so the response cannot be used to probe for archives outside the guest's scope."
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archives.rs:225"
      }
    },
    "/api/archives/{id}/files/thumbnails": {
      "post": {
        "description": "Create thumbnails for every page of a given Archive.   This endpoint will queue generation of the thumbnails in the background.  \n\nIf all thumbnails are detected as already existing, the call will return HTTP code 200.  \nThis endpoint can be called multiple times -- If a thumbnailing job is already in progress for the given ID, it'll just give you the ID for that ongoing job.",
        "operationId": "queueArchivePageThumbnailExtraction",
        "parameters": [
          {
            "description": "ID of the Archive to process.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/ArchiveId"
            }
          },
          {
            "description": "Whether to force regeneration of all thumbnails even if they already exist.",
            "in": "query",
            "name": "force",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "message": "No job queued, all thumbnails already exist.",
                    "operation": "generate_page_thumbnails",
                    "success": 1
                  },
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "operation": {
                      "description": "Name of operation",
                      "enum": [
                        "generate_page_thumbnails"
                      ],
                      "type": "string"
                    },
                    "success": {
                      "description": "Returns 1 if operation was successful, else 0",
                      "enum": [
                        0,
                        1
                      ],
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "If thumbnails are already extracted and `force=0`"
          },
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MinionJobResponse"
                }
              }
            },
            "description": "The thumbnails are queued for extraction. You can use `/api/minion/:jobid` to track progress, by looking at `notes->progress` and `notes->pages`."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "No archive ID response"
          }
        },
        "summary": "Extract page thumbnails",
        "tags": [
          "archives"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archives.rs:227",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/archives/{id}/isnew": {
      "delete": {
        "description": "Clears the \"New!\" flag on an archive.",
        "operationId": "clearNewArchiveFlag",
        "parameters": [
          {
            "description": "ID of the Archive to process.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/ArchiveId"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "id": {
                      "$ref": "#/components/schemas/ArchiveId",
                      "description": "ID of the archive"
                    },
                    "operation": {
                      "description": "Name of the operation",
                      "enum": [
                        "clear_new"
                      ],
                      "type": "string"
                    },
                    "success": {
                      "description": "Returns 1 if successful, else 0",
                      "enum": [
                        0,
                        1
                      ],
                      "type": "integer"
                    }
                  },
                  "required": [
                    "id",
                    "operation",
                    "success"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Successful response"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "No archive ID response"
          },
          "423": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Locked resource response"
          }
        },
        "summary": "Clear Archive New flag",
        "tags": [
          "archives"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archives.rs:248",
        "x-lanrurugi-token-role": "admin"
      },
      "put": {
        "description": "Sets/Restores the \"New!\" flag on an archive.",
        "operationId": "setNewArchiveFlag",
        "parameters": [
          {
            "description": "ID of the Archive to process.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/ArchiveId"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Successful response"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "No archive ID response"
          },
          "423": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Locked resource response"
          }
        },
        "summary": "Set Archive New flag",
        "tags": [
          "archives"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archives.rs:248",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/archives/{id}/metadata": {
      "get": {
        "description": "Get Metadata (title, tags) for a given Archive.\n\n<!-- lanrurugi:access-notes -->\n\n**Access differs by role**\n\n- For a guest visitor whose scope excludes this archive, this answers **404**, not 403 — deliberately indistinguishable from an archive that does not exist, so the response cannot be used to probe for archives outside the guest's scope.",
        "operationId": "getArchiveMetadata",
        "parameters": [
          {
            "description": "ID of the Archive to process.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/ArchiveId"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArchiveMetadataJson"
                }
              }
            },
            "description": "Archive metadata"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "No archive ID response"
          }
        },
        "summary": "Get archive metadata",
        "tags": [
          "archives"
        ],
        "x-lanrurugi-access-notes": [
          "For a guest visitor whose scope excludes this archive, this answers **404**, not 403 — deliberately indistinguishable from an archive that does not exist, so the response cannot be used to probe for archives outside the guest's scope."
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archives.rs:220"
      },
      "put": {
        "description": "Update tags and title for the given Archive.  \nData supplied to the server through this method will **overwrite** the previous data.",
        "operationId": "updateArchiveMetadata",
        "parameters": [
          {
            "description": "ID of the Archive to process.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/ArchiveId"
            }
          },
          {
            "description": "New Title of the Archive.",
            "in": "query",
            "name": "title",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "New Tags of the Archive.",
            "in": "query",
            "name": "tags",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "New Summary of the Archive.",
            "in": "query",
            "name": "summary",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "operation": "update_metadata",
                  "success": 1
                },
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Archive metadata updated"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "No archive ID response"
          },
          "423": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Locked resource response"
          }
        },
        "summary": "Update archive metadata",
        "tags": [
          "archives"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archives.rs:220",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/archives/{id}/page": {
      "get": {
        "description": "Get an archive page. This call is mainly used alongside `/api/archives/files`.\n\n<!-- lanrurugi:access-notes -->\n\n**Access differs by role**\n\n- For a guest visitor whose scope excludes this archive, this answers **404**, not 403 — deliberately indistinguishable from an archive that does not exist, so the response cannot be used to probe for archives outside the guest's scope.",
        "operationId": "getPage",
        "parameters": [
          {
            "description": "ID of the Archive to download.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/ArchiveId"
            }
          },
          {
            "description": "Path to the image.",
            "in": "query",
            "name": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/octet-stream": {
                "schema": {
                  "format": "binary",
                  "type": "string"
                }
              }
            },
            "description": "Archive page"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "No archive ID response"
          }
        },
        "summary": "Get an archive page",
        "tags": [
          "archives"
        ],
        "x-lanrurugi-access-notes": [
          "For a guest visitor whose scope excludes this archive, this answers **404**, not 403 — deliberately indistinguishable from an archive that does not exist, so the response cannot be used to probe for archives outside the guest's scope."
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archives.rs:224"
      }
    },
    "/api/archives/{id}/page-dimensions": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/archives.rs:3301), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`GET /archives/{id}/page-dimensions?count=N` — natural pixel width/height for the first `N`\npages only, in the same order as `/files`'s own `pages` array (so the frontend can zip them\ntogether by index without a separate name-matching step). Additive to `/files`, not folded into\nit — existing callers of `/files` (the third-party-compatible file-list contract) shouldn't have\nto pay for an archive pass with per-entry image-header parsing they never asked for; only the\ninfinite-scroll reader view (the one caller that actually needs this, to size not-yet-loaded\n`<img>`s accurately before jumping to a resume position — see `read_page_dimensions`'s own docs\nfor why it's bounded to just the pages before that jump, not the whole archive) calls this\nendpoint at all.\n\n<!-- lanrurugi:access-notes -->\n\n**Access differs by role**\n\n- For a guest visitor whose scope excludes this archive, this answers **404**, not 403 — deliberately indistinguishable from an archive that does not exist, so the response cannot be used to probe for archives outside the guest's scope.",
        "operationId": "getPageDimensions",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/archives.rs:226."
          }
        },
        "summary": "`GET /archives/{id}/page-dimensions?count=N` — natural pixel width/height for the first `N`",
        "tags": [
          "archives"
        ],
        "x-lanrurugi-access-notes": [
          "For a guest visitor whose scope excludes this archive, this answers **404**, not 403 — deliberately indistinguishable from an archive that does not exist, so the response cannot be used to probe for archives outside the guest's scope."
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archives.rs:226",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/archives/{id}/page/{page}/text-regions": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/translation.rs:406), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nDetection output plus everything the browser needs to translate and composite locally.\n\nIncludes the same `context` the cloud path assembles, so a locally-hosted backend gets\nFR-007a–e's consistency benefit even though its translation call never touches this server.",
        "operationId": "getPageTextRegions",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/translation.rs:45."
          }
        },
        "summary": "Detection output plus everything the browser needs to translate and composite locally",
        "tags": [
          "translation"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/translation.rs:45",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/archives/{id}/page/{page}/translation": {
      "delete": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/translation.rs:203), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nReader's \"重新翻译\": drops this page's detection cache, every translated record and the\nrendered image, so the next `GET .../translation` re-runs the whole OCR -> translate ->\ncomposite pipeline instead of reusing a stale cache (e.g. one produced by an old pipeline\nversion, or a partially-detected region set).",
        "operationId": "clearPageTranslation",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/translation.rs:41."
          }
        },
        "summary": "Reader's \"重新翻译\": drops this page's detection cache, every translated record and the",
        "tags": [
          "translation"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/translation.rs:41",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      },
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/translation.rs:275), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nServes the composited, translated page for the cloud-backend path (T028).\n\nReturns the cached rendering when one exists. On a miss it **triggers** the real orchestration —\nOCR → font routing → batched translation → compositing → cache write\n(`translation_pipeline::translate_page`) — for this page and its look-ahead window, then reports\nnot-ready immediately.\n\nThe work is deliberately not awaited here: a full OCR + LLM cycle takes tens of seconds, and\nFR-012 requires the reader to keep showing the original page with a non-blocking indicator\nmeanwhile. The client polls; a later poll finds the cache entry this triggered. Reading must\nnever wait on translation (SC-007).",
        "operationId": "getPageTranslation",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/translation.rs:41."
          }
        },
        "summary": "Serves the composited, translated page for the cloud-backend path (T028)",
        "tags": [
          "translation"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/translation.rs:41",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/archives/{id}/page/{page}/translation/record": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/translation.rs:587), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nRecords a translation the browser obtained from its own local model (T049a).\n\n**No LLM call happens here** — this is a lightweight write. Its purpose is that a name/term the\nlocal backend translated still reaches the shared, server-stored glossary, so later cloud-path\n(or other-device) requests for the same volume stay consistent with it.",
        "operationId": "recordLocalTranslation",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/translation.rs:49."
          }
        },
        "summary": "Records a translation the browser obtained from its own local model (T049a)",
        "tags": [
          "translation"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/translation.rs:49",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/archives/{id}/patch": {
      "delete": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/archives.rs:1111), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`DELETE /archives/{id}/patch` — deletes the sidecar `.patch.zip` (if any) and clears the\narchive's `has_patch` flag. Lets the user undo a patch — e.g. to replace it with pages from a\ndifferent source after re-running the comparison flow — without touching the archive itself.",
        "operationId": "deletePatch",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/archives.rs:246."
          }
        },
        "summary": "`DELETE /archives/{id}/patch` — deletes the sidecar `.patch.zip` (if any) and clears the",
        "tags": [
          "archives"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archives.rs:246",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/archives/{id}/progress/{page}": {
      "put": {
        "description": "Tell the server which page of this Archive you're currently showing/reading, so that it updates its internal reading progression accordingly.  \nThis endpoint will also update the date this Archive was last read, using the current server timestamp.  \n\nYou should call this endpoint only when you're sure the user is currently reading the page you present.  \n**Don't** use it when preloading images off the server.\n\nWhether to make reading progression regressible or not is up to the client. (The web client will reduce progression if the user starts reading previous pages)  \nConsider however removing the \"New!\" flag from an archive when you start updating its progress - The web client won't display any reading progression if the new flag is still set.  \n\n⚠ If the server is configured to use clientside progress tracking, this API call will return an error!  \nMake sure to check using `/api/info` whether the server tracks reading progression or not before calling this endpoint.",
        "operationId": "updateArchiveProgress",
        "parameters": [
          {
            "description": "ID of the Archive to process.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/ArchiveId"
            }
          },
          {
            "description": "Current page to update the reading progress to.  \n**Must** be a positive integer, and inferior or equal to the total page number of the archive.",
            "in": "path",
            "name": "page",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "id": "75d18ce470dc99f83dc355bdad66319d1f33c82b",
                    "lastreadtime": 123943543,
                    "operation": "update_progress",
                    "page": 34,
                    "success": 1
                  },
                  "properties": {
                    "id": {
                      "$ref": "#/components/schemas/ArchiveId",
                      "description": "ID of the archive updated"
                    },
                    "lastreadtime": {
                      "description": "Last read time in epoch seconds",
                      "type": "integer"
                    },
                    "operation": {
                      "description": "Name of the operation",
                      "enum": [
                        "update_progress"
                      ],
                      "type": "string"
                    },
                    "page": {
                      "description": "Updated reading progress",
                      "type": "integer"
                    },
                    "success": {
                      "description": "Returns 1 if successful, else 0",
                      "enum": [
                        0,
                        1
                      ],
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Success response"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Server-side Progress Tracking is disabled on this instance.",
                  "operation": "update_progress",
                  "success": 0
                },
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Generic error response"
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "This operation requires authentication.",
                  "operation": "update_progress",
                  "success": 0
                },
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Authentication required when authprogress is enabled"
          },
          "423": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Locked resource response"
          }
        },
        "summary": "Update reading progression",
        "tags": [
          "archives"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archives.rs:252",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/archives/{id}/rename": {
      "put": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/archives.rs:1228), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`PUT /archives/{id}/rename` — additive, no legacy equivalent (legacy has no archive-rename\nfeature at all; verified via `~/LANraragi/lib/LANraragi/Controller/*.pm` and `public/js/edit.js`\nhaving no `rename` reference anywhere). Renames the archive's on-disk file (not just its stored\n`title`/`name`), keeping `Archive::file` and `LRR_FILEMAP` in sync — modeled on\n`download_manager::ingest`'s own staging-file-to-`archive_dir` rename fixup, the closest\nexisting precedent for \"the archive's file path changed, update the two places that track it\".\n\nA sidecar `.patch.zip` (see `delete_patch`'s own docs on how it's associated — same directory,\nsame stem, purely by filename convention) is renamed alongside the archive itself: leaving it\nunder the old stem would silently break `patch::patch_path_for`'s lookup for every future\nrequest, making an existing patch invisible without deleting anything or erroring — the kind of\nsilent data-loss-adjacent bug that's much worse than a rename this function could instead just\nreject outright.",
        "operationId": "renameArchive",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/archives.rs:247."
          }
        },
        "summary": "`PUT /archives/{id}/rename` — additive, no legacy equivalent (legacy has no archive-rename",
        "tags": [
          "archives"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archives.rs:247",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/archives/{id}/split-suggestion": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `get_split_suggestion` in crates/lanrurugi-api/src/archive_split.rs:687.",
        "operationId": "getSplitSuggestion",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/archive_split.rs:69."
          }
        },
        "summary": "get_split_suggestion",
        "tags": [
          "archive_split"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archive_split.rs:69",
        "x-lanrurugi-undocumented": true
      },
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/archive_split.rs:661), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nManual \"analyze this archive now\" endpoint.",
        "operationId": "analyzeSplitSuggestion",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/archive_split.rs:69."
          }
        },
        "summary": "Manual \"analyze this archive now\" endpoint",
        "tags": [
          "archive_split"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archive_split.rs:69",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/archives/{id}/split-tree": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/archive_split.rs:508), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`GET /archives/{id}/split-tree` — returns the archive's internal directory tree with nested\narchives recursively expanded. This is a viewer-only endpoint: it does not call the LLM and\ndoes not create any output ZIPs.",
        "operationId": "getSplitTree",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/archive_split.rs:75."
          }
        },
        "summary": "`GET /archives/{id}/split-tree` — returns the archive's internal directory tree with nested",
        "tags": [
          "archive_split"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archive_split.rs:75",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/archives/{id}/split/execute": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `execute_split` in crates/lanrurugi-api/src/archive_split.rs:715.",
        "operationId": "executeSplit",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/archive_split.rs:73."
          }
        },
        "summary": "execute_split",
        "tags": [
          "archive_split"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archive_split.rs:73",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/archives/{id}/split/stream": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `split_progress_stream` in crates/lanrurugi-api/src/archive_split.rs:773.",
        "operationId": "splitProgressStream",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/archive_split.rs:74."
          }
        },
        "summary": "split_progress_stream",
        "tags": [
          "archive_split"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archive_split.rs:74",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/archives/{id}/stamps": {
      "get": {
        "description": "Get pages that contain at least one stamp in the archive.\n\n<!-- lanrurugi:access-notes -->\n\n**Access differs by role**\n\n- For a guest visitor whose scope excludes this archive, this answers **404**, not 403 — deliberately indistinguishable from an archive that does not exist, so the response cannot be used to probe for archives outside the guest's scope.",
        "operationId": "stampedPages",
        "parameters": [
          {
            "description": "ID of the archive.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StampsResponse"
                }
              }
            },
            "description": "Success response"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Error response"
          }
        },
        "summary": "Get pages that contain at least one stamp in the archive",
        "tags": [
          "stamps"
        ],
        "x-lanrurugi-access-notes": [
          "For a guest visitor whose scope excludes this archive, this answers **404**, not 403 — deliberately indistinguishable from an archive that does not exist, so the response cannot be used to probe for archives outside the guest's scope."
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/stamps.rs:34"
      }
    },
    "/api/archives/{id}/stamps/{index}": {
      "get": {
        "description": "Get the stamps linked to the page.\n\n<!-- lanrurugi:access-notes -->\n\n**Access differs by role**\n\n- For a guest visitor whose scope excludes this archive, this answers **404**, not 403 — deliberately indistinguishable from an archive that does not exist, so the response cannot be used to probe for archives outside the guest's scope.",
        "operationId": "stampsByPage",
        "parameters": [
          {
            "description": "ID of the archive.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Page of the archive.",
            "in": "path",
            "name": "index",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "result": {
                      "items": {
                        "$ref": "#/components/schemas/StampsData"
                      },
                      "type": "array"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Success response"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Error response"
          }
        },
        "summary": "Get the stamps linked to the page",
        "tags": [
          "stamps"
        ],
        "x-lanrurugi-access-notes": [
          "For a guest visitor whose scope excludes this archive, this answers **404**, not 403 — deliberately indistinguishable from an archive that does not exist, so the response cannot be used to probe for archives outside the guest's scope."
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/stamps.rs:35"
      },
      "put": {
        "description": "Add a new Stamp to the page at the given coordinates.",
        "operationId": "addStamp",
        "parameters": [
          {
            "description": "ID of the archive.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Page of the archive.",
            "in": "path",
            "name": "index",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "description": "Text of the stamp.",
            "in": "query",
            "name": "content",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Position of the stamp in the page.",
            "in": "query",
            "name": "position",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "operation": {
                      "enum": [
                        "add_stamp"
                      ],
                      "type": "string"
                    },
                    "stamp_id": {
                      "description": "Stamp ID",
                      "type": "string"
                    },
                    "success": {
                      "enum": [
                        0,
                        1
                      ],
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Success response"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Error response"
          }
        },
        "summary": "Add a stamp annotation",
        "tags": [
          "stamps"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/stamps.rs:35",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/archives/{id}/tankoubons": {
      "get": {
        "description": "Get all the Tankoubons which currently refer to this Archive ID.",
        "operationId": "getArchiveTankoubons",
        "parameters": [
          {
            "description": "ID of the Archive to process.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/ArchiveId"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "operation": "find_arc_tankoubons",
                    "success": 1,
                    "tankoubons": [
                      "TANK_1688616437",
                      "TANK_1688693913"
                    ]
                  },
                  "properties": {
                    "operation": {
                      "description": "Name of the operation",
                      "enum": [
                        "find_arc_tankoubons"
                      ],
                      "type": "string"
                    },
                    "success": {
                      "description": "Returns 1 if operation was successful, else 0",
                      "enum": [
                        0,
                        1
                      ],
                      "type": "integer"
                    },
                    "tankoubons": {
                      "description": "Array of tankoubon IDs",
                      "items": {
                        "$ref": "#/components/schemas/TankoubonId"
                      },
                      "type": "array"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Archive tankoubons"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "No archive ID response"
          }
        },
        "summary": "Get archive tankoubons",
        "tags": [
          "archives"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archives.rs:240"
      }
    },
    "/api/archives/{id}/thumbnail": {
      "get": {
        "description": "Get a Thumbnail image for a given Archive. This endpoint will return a placeholder image if it doesn't already exist. If you want to queue generation of the thumbnail in the background, you can use the no_fallback query parameter. This will give you a background job ID instead of the placeholder.\n\n<!-- lanrurugi:access-notes -->\n\n**Access differs by role**\n\n- For a guest visitor whose scope excludes this archive, this answers **404**, not 403 — deliberately indistinguishable from an archive that does not exist, so the response cannot be used to probe for archives outside the guest's scope.",
        "operationId": "getArchiveThumbnail",
        "parameters": [
          {
            "description": "ID of the archive to process",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/ArchiveId"
            }
          },
          {
            "description": "Disables the placeholder image, queues the thumbnail for extraction and returns a JSON with code 202. This parameter does nothing if the image already exists. (You will get the image with code 200 no matter what)",
            "in": "query",
            "name": "no_fallback",
            "required": false,
            "schema": {
              "enum": [
                "true",
                "false"
              ],
              "type": "string"
            }
          },
          {
            "description": "Specify which page you want to get a thumbnail for. Defaults to the cover, aka page 1.",
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/octet-stream": {
                "schema": {
                  "format": "binary",
                  "type": "string"
                }
              }
            },
            "description": "If the thumbnail was already extracted, you get it directly."
          },
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MinionJobResponse"
                }
              }
            },
            "description": "The thumbnail is queued for extraction. Use `/api/minion/:jobid` to track when your thumbnail is ready."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "No archive ID specified.",
                  "operation": "serve_thumbnail",
                  "success": 0
                },
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "No archive ID response"
          }
        },
        "summary": "Get archive thumbnail",
        "tags": [
          "archives"
        ],
        "x-lanrurugi-access-notes": [
          "For a guest visitor whose scope excludes this archive, this answers **404**, not 403 — deliberately indistinguishable from an archive that does not exist, so the response cannot be used to probe for archives outside the guest's scope."
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archives.rs:235"
      },
      "put": {
        "description": "Update the cover thumbnail for the given Archive. You can specify a page number to use as the thumbnail, or you can use the default thumbnail.",
        "operationId": "updateArchiveThumbnail",
        "parameters": [
          {
            "description": "ID of the Archive to process.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/ArchiveId"
            }
          },
          {
            "description": "Page you want to make the thumbnail out of. Defaults to 1.",
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "new_thumbnail": {
                      "description": "New thumbnail URL",
                      "type": "string"
                    },
                    "operation": {
                      "description": "Name of operation",
                      "enum": [
                        "update_thumbnail"
                      ],
                      "type": "string"
                    },
                    "success": {
                      "description": "Returns 1 if operation was successful, else 0",
                      "enum": [
                        0,
                        1
                      ],
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Archive thumbnail updated"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "No archive ID specified.",
                  "operation": "update_thumbnail",
                  "success": 0
                },
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "No archive ID response"
          }
        },
        "summary": "Update archive thumbnail",
        "tags": [
          "archives"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archives.rs:235",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/archives/{id}/thumbnails": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `get_page_thumbnails` in crates/lanrurugi-api/src/archives.rs:3483.\n\n<!-- lanrurugi:access-notes -->\n\n**Access differs by role**\n\n- For a guest visitor whose scope excludes this archive, this answers **404**, not 403 — deliberately indistinguishable from an archive that does not exist, so the response cannot be used to probe for archives outside the guest's scope.",
        "operationId": "getPageThumbnails",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/archives.rs:231."
          }
        },
        "summary": "get_page_thumbnails",
        "tags": [
          "archives"
        ],
        "x-lanrurugi-access-notes": [
          "For a guest visitor whose scope excludes this archive, this answers **404**, not 403 — deliberately indistinguishable from an archive that does not exist, so the response cannot be used to probe for archives outside the guest's scope."
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archives.rs:231",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/archives/{id}/toc": {
      "delete": {
        "description": "Delete an entry from the Table of Contents of a given Archive.  ",
        "operationId": "deleteArchiveToc",
        "parameters": [
          {
            "description": "ID of the Archive to process.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/ArchiveId"
            }
          },
          {
            "description": "Page number of the chapter/section to delete.",
            "in": "query",
            "name": "page",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "operation": "remove_toc",
                  "success": 1,
                  "successMessage": "Removed ToC entry for page 143."
                },
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Archive TOC updated"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "No archive ID response or other error"
          },
          "423": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Locked resource response"
          }
        },
        "summary": "Remove entry from Archive Table of Contents",
        "tags": [
          "archives"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archives.rs:241",
        "x-lanrurugi-token-role": "admin"
      },
      "put": {
        "description": "Add an entry to the Table of Contents of a given Archive.   The ToC is stored as a JSON-encoded key-value array mapping a page to a title for the chapter/section starting at that page.  ",
        "operationId": "addArchiveToc",
        "parameters": [
          {
            "description": "ID of the Archive to process.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/ArchiveId"
            }
          },
          {
            "description": "Page number where the chapter/section starts.",
            "in": "query",
            "name": "page",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "description": "Title of the chapter/section.",
            "in": "query",
            "name": "title",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "operation": "update_toc",
                  "success": 1,
                  "successMessage": "Added ToC entry for page 143."
                },
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Archive TOC updated"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "No archive ID response or other error"
          },
          "423": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Locked resource response"
          }
        },
        "summary": "Add entry to Archive Table of Contents",
        "tags": [
          "archives"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archives.rs:241",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/archives/{id}/translation/scope": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/translation.rs:118), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nReports whether translation is on for this archive, and whether that's this archive's own\nsetting or inherited from a Tankoubon it belongs to — the latter so the reader can tell the\nuser \"this follows the whole book's setting\" rather than presenting it as if toggling here only\never affects this one file.",
        "operationId": "getTranslationScope",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/translation.rs:53."
          }
        },
        "summary": "Reports whether translation is on for this archive, and whether that's this archive's own",
        "tags": [
          "translation"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/translation.rs:53",
        "x-lanrurugi-undocumented": true
      },
      "put": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/translation.rs:144), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nSets the switch for whichever scope actually governs this archive right now (its Tankoubon if\nit has one, else the archive itself) — see `TranslationScopeRepository::set_enabled`'s own docs\nfor why toggling here only ever targets one or the other, never both.",
        "operationId": "updateTranslationScope",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/translation.rs:53."
          }
        },
        "summary": "Sets the switch for whichever scope actually governs this archive right now (its Tankoubon if",
        "tags": [
          "translation"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/translation.rs:53",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/auth/bridge/callback": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `bridge_callback` in crates/lanrurugi-api/src/auth_bridge.rs:599.",
        "operationId": "bridgeCallback",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/auth_bridge.rs:47."
          }
        },
        "security": [],
        "summary": "bridge_callback",
        "tags": [
          "auth_bridge"
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/auth_bridge.rs:47",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/auth/bridge/prepare": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `prepare_bridge` in crates/lanrurugi-api/src/auth_bridge.rs:402.",
        "operationId": "prepareBridge",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/auth_bridge.rs:45."
          }
        },
        "security": [],
        "summary": "prepare_bridge",
        "tags": [
          "auth_bridge"
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/auth_bridge.rs:45",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/auth/bridge/start": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `start_bridge` in crates/lanrurugi-api/src/auth_bridge.rs:464.",
        "operationId": "startBridge",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/auth_bridge.rs:46."
          }
        },
        "security": [],
        "summary": "start_bridge",
        "tags": [
          "auth_bridge"
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/auth_bridge.rs:46",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/auth/config": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `get_auth_config` in crates/lanrurugi-api/src/auth_bridge.rs:380.",
        "operationId": "getAuthConfig",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/auth_bridge.rs:44."
          }
        },
        "security": [],
        "summary": "get_auth_config",
        "tags": [
          "auth_bridge"
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/auth_bridge.rs:44",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/bench/run": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/bench.rs:65), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nRuns the cross-system comparison against the caller-supplied legacy/new endpoints (both are\nexternal HTTP targets from this crate's point of view — even when \"new\" happens to be this\nsame running instance, reached via its own bind address — so one implementation drives either\ncase identically, matching how `lanrurugi-bench-compare` works standalone).",
        "operationId": "runBench",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/bench.rs:20."
          }
        },
        "summary": "Runs the cross-system comparison against the caller-supplied legacy/new endpoints (both are",
        "tags": [
          "bench"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/bench.rs:20",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/bench/{reportid}": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `get_report` in crates/lanrurugi-api/src/bench.rs:107.",
        "operationId": "getReport",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/bench.rs:21."
          }
        },
        "summary": "get_report",
        "tags": [
          "bench"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/bench.rs:21",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/bookmarks": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/bookmarks.rs:252), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`GET /bookmarks` — every bookmark, aggregated by archive, sorted and paginated per `sort`/\n`cursor`/`limit`. A bookmark whose archive has since been deleted is silently dropped from the\nresult rather than erroring the whole list (same tolerance `BookmarksRepository::list_all`\nalready applies to malformed individual entries — one dangling reference shouldn't take down\nevery other still-valid bookmark).",
        "operationId": "listBookmarks",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/bookmarks.rs:29."
          }
        },
        "summary": "`GET /bookmarks` — every bookmark, aggregated by archive, sorted and paginated per `sort`/",
        "tags": [
          "bookmarks"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/bookmarks.rs:29",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/bookmarks/hover-page-order": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/bookmarks.rs:984), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`GET /bookmarks/hover-page-order` — the saved `BookmarkHoverGrid` page-order preference\n(`None` when never set; the frontend applies its own default, `\"pageDesc\"`, in that case).",
        "operationId": "getHoverPageOrder",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/bookmarks.rs:48."
          }
        },
        "summary": "`GET /bookmarks/hover-page-order` — the saved `BookmarkHoverGrid` page-order preference",
        "tags": [
          "bookmarks"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/bookmarks.rs:48",
        "x-lanrurugi-undocumented": true
      },
      "put": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/bookmarks.rs:1002), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nNot recorded to the activity log — a display-only preference, same posture `settings.rs`'s own\n`theme` field takes (no `record_manual` call either).",
        "operationId": "putHoverPageOrder",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/bookmarks.rs:48."
          }
        },
        "summary": "Not recorded to the activity log — a display-only preference, same posture `settings.rs`'s own",
        "tags": [
          "bookmarks"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/bookmarks.rs:48",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/bookmarks/only-matching": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/bookmarks.rs:1018), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`GET /bookmarks/only-matching` — the saved \"only show bookmarks that themselves matched `q`\"\npreference (`false` when never set, matching the historical always-show-everything behavior).",
        "operationId": "getOnlyMatchingBookmarks",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/bookmarks.rs:52."
          }
        },
        "summary": "`GET /bookmarks/only-matching` — the saved \"only show bookmarks that themselves matched `q`\"",
        "tags": [
          "bookmarks"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/bookmarks.rs:52",
        "x-lanrurugi-undocumented": true
      },
      "put": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/bookmarks.rs:1035), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nNot recorded to the activity log — same display-only posture `put_hover_page_order` takes.",
        "operationId": "putOnlyMatchingBookmarks",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/bookmarks.rs:52."
          }
        },
        "summary": "Not recorded to the activity log — same display-only posture `put_hover_page_order` takes",
        "tags": [
          "bookmarks"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/bookmarks.rs:52",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/categories": {
      "get": {
        "description": "Get all the categories saved on the server.",
        "operationId": "getCategoryList",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/CategoryMetadataJson"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Categories"
          }
        },
        "summary": "Get all Categories",
        "tags": [
          "categories"
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/categories.rs:35"
      },
      "put": {
        "description": "Create a new Category.",
        "operationId": "createCategory",
        "requestBody": {
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "properties": {
                  "name": {
                    "description": "Name of the Category.",
                    "type": "string"
                  },
                  "pinned": {
                    "description": "Add this parameter if you want the created category to be pinned.",
                    "type": "boolean"
                  },
                  "search": {
                    "description": "Matching predicate, if creating a Dynamic Category.",
                    "type": "string"
                  }
                },
                "required": [
                  "name"
                ],
                "type": "object"
              }
            },
            "multipart/form-data": {
              "schema": {
                "properties": {
                  "name": {
                    "description": "Name of the Category.",
                    "type": "string"
                  },
                  "pinned": {
                    "description": "Add this parameter if you want the created category to be pinned.",
                    "type": "boolean"
                  },
                  "search": {
                    "description": "Matching predicate, if creating a Dynamic Category.",
                    "type": "string"
                  }
                },
                "required": [
                  "name"
                ],
                "type": "object"
              }
            }
          },
          "required": false
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "category_id": {
                      "$ref": "#/components/schemas/CategoryId",
                      "description": "ID of the created Category"
                    },
                    "operation": {
                      "enum": [
                        "create_category"
                      ],
                      "type": "string"
                    },
                    "success": {
                      "enum": [
                        0,
                        1
                      ],
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Success response"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Error response"
          }
        },
        "summary": "Create a Category",
        "tags": [
          "categories"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/categories.rs:35",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/categories/{id}": {
      "delete": {
        "operationId": "deleteCategory",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Success response"
          },
          "423": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "The category ID is currently locked for modification"
          }
        },
        "summary": "Delete Category",
        "tags": [
          "categories"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/categories.rs:36",
        "x-lanrurugi-token-role": "admin"
      },
      "get": {
        "description": "Get the details of the specified category ID.",
        "operationId": "getCategory",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CategoryMetadataJson"
                }
              }
            },
            "description": "Category"
          }
        },
        "summary": "Get a single Category",
        "tags": [
          "categories"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/categories.rs:36"
      },
      "put": {
        "description": "Modify a Category.",
        "operationId": "updateCategory",
        "requestBody": {
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "properties": {
                  "name": {
                    "description": "New name of the Category",
                    "type": "string"
                  },
                  "pinned": {
                    "description": "Add this argument to pin the Category. If you don't, the category will be unpinned on update.",
                    "type": "boolean"
                  },
                  "search": {
                    "description": "Predicate. Trying to add a predicate to a category that already contains Archives will give you an error.",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            },
            "multipart/form-data": {
              "schema": {
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "pinned": {
                    "type": "boolean"
                  },
                  "search": {
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          },
          "required": false
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "category_id": {
                      "$ref": "#/components/schemas/CategoryId"
                    },
                    "operation": {
                      "enum": [
                        "update_category"
                      ],
                      "type": "string"
                    },
                    "success": {
                      "enum": [
                        0,
                        1
                      ],
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Success response"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Error response"
          },
          "423": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "The Category is currently locked for modification"
          }
        },
        "summary": "Update Category",
        "tags": [
          "categories"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/categories.rs:36",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/categories/{id}/{archive}": {
      "delete": {
        "description": "Remove an Archive ID from a Category.",
        "operationId": "removeFromCategory",
        "parameters": [
          {
            "description": "Category ID to remove from.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/CategoryId"
            }
          },
          {
            "description": "Archive ID to remove.",
            "in": "path",
            "name": "archive",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/ArchiveId"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Result"
          },
          "423": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "The category ID is currently locked for modification"
          }
        },
        "summary": "Remove an Archive from a Category",
        "tags": [
          "categories"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/categories.rs:42",
        "x-lanrurugi-token-role": "admin"
      },
      "put": {
        "description": "Adds the specified Archive ID (see Archive API) to the given Category.",
        "operationId": "addToCategory",
        "parameters": [
          {
            "description": "Category ID to add to.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/CategoryId"
            }
          },
          {
            "in": "path",
            "name": "archive",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/ArchiveId"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Result"
          },
          "423": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "The category ID is currently locked for modification"
          }
        },
        "summary": "Add an Archive to a Category",
        "tags": [
          "categories"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/categories.rs:42",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/database/backup": {
      "get": {
        "description": "Scans the entire database and returns a backup in JSON form.   Consider using the POST variant if your database is large, as this basic GET endpoint might time out if it takes too long to generate the backup.  \nThis backup can be reimported manually through the Backup and Restore feature.",
        "operationId": "getBackupJson",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "archives": [
                      {
                        "arcid": "28697b96f0ac5858be2614ed10ca47742c9522fd",
                        "filename": "FateGOMEMO",
                        "summary": null,
                        "tags": "parody:fate grand order, group:wadamemo, artist:wada rco, artbook, full color, super:test, date_added:1553537258",
                        "thumbhash": "ec2a0ca3a3da67a9390889f0910fe494241faa9a",
                        "title": "Fate GO MEMO"
                      },
                      {
                        "arcid": "d1858d5dc36925aa66be072a97817650d39de166",
                        "filename": "(Memes)9B789D38B0784C5FBC9D59A9F24D20F2",
                        "summary": null,
                        "tags": "",
                        "thumbhash": "9846bb6d62949b56545c42e88d8010446b65702e",
                        "title": "test title"
                      }
                    ],
                    "categories": [
                      {
                        "archives": [],
                        "catid": "SET_1749925633",
                        "name": "english",
                        "search": "language:english"
                      }
                    ],
                    "tankoubons": [
                      {
                        "archives": [
                          "28697b96f0ac5858be2614ed10ca47742c9522fd",
                          "d1858d5dc36925aa66be072a97817650d39de166"
                        ],
                        "name": "sailor moon",
                        "tankid": "TANK_1749925516"
                      }
                    ]
                  },
                  "properties": {
                    "archives": {
                      "description": "Array of archive metadata",
                      "items": {
                        "$ref": "#/components/schemas/BackupArchiveMetadataJson"
                      },
                      "type": "array"
                    },
                    "categories": {
                      "description": "Array of category metadata",
                      "items": {
                        "$ref": "#/components/schemas/BackupCategoryMetadataJson"
                      },
                      "type": "array"
                    },
                    "tankoubons": {
                      "description": "Array of tankoubon metadata",
                      "items": {
                        "$ref": "#/components/schemas/TankoubonBackupJson"
                      },
                      "type": "array"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Success response"
          }
        },
        "summary": "Get a backup JSON",
        "tags": [
          "database"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/database.rs:38"
      },
      "post": {
        "description": "Queues a Minion job to generate a backup JSON file.  \nUse the returned job ID to check progress and download the file once complete.",
        "operationId": "queueBackupJob",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "job": 42,
                    "operation": "queue_backup",
                    "success": 1
                  },
                  "properties": {
                    "job": {
                      "type": "integer"
                    },
                    "operation": {
                      "enum": [
                        "queue_backup"
                      ],
                      "type": "string"
                    },
                    "success": {
                      "enum": [
                        0,
                        1
                      ],
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Enqueued job"
          }
        },
        "summary": "Queue a backup job",
        "tags": [
          "database"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/database.rs:38",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/database/backup/{jobid}": {
      "get": {
        "description": "Downloads the backup JSON file generated by a completed backup job.",
        "operationId": "downloadBackup",
        "parameters": [
          {
            "description": "ID of the completed backup job.",
            "in": "path",
            "name": "jobid",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "description": "Format of the returned backup. Defaults to `file`. `json` will return the backup as a JSON response.",
            "in": "query",
            "name": "format",
            "required": false,
            "schema": {
              "enum": [
                "json",
                "file"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Backup JSON file"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Job not found or not completed"
          }
        },
        "summary": "Download backup JSON from completed job",
        "tags": [
          "database"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/database.rs:39"
      }
    },
    "/api/database/clean": {
      "post": {
        "description": "Cleans the Database, hiding then removing entries for files that are no longer on the filesystem.  \nEntries that are no longer on the filesystem are only unlinked at first so they don't appear in the UI -- A subsequent run of this cleanup will **delete** unlinked entries.",
        "operationId": "cleanDatabase",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "deleted": {
                      "description": "Amount of unlinked DB entries that were deleted",
                      "type": "integer"
                    },
                    "operation": {
                      "description": "Name of the operation",
                      "type": "string"
                    },
                    "success": {
                      "description": "Returns 1 if operation successful, else 0.",
                      "enum": [
                        0,
                        1
                      ],
                      "type": "integer"
                    },
                    "unlinked": {
                      "description": "Amount of live DB entries that got unlinked - Run Clean again to delete them.",
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Success response"
          }
        },
        "summary": "Clean the Database",
        "tags": [
          "database"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/database.rs:42",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/database/drop": {
      "post": {
        "description": "Delete the entire database, including user preferences.\nThis is a rather dangerous endpoint, invoking it might lock you out of the server as a client!",
        "operationId": "dropDatabase",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Success response"
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "summary": "Drop the Database",
        "tags": [
          "database"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/database.rs:36"
      }
    },
    "/api/database/duplicates": {
      "delete": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/duplicates.rs:227), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`DELETE /database/duplicates` — matches legacy's `?delete=1` param on the same page route.",
        "operationId": "clearDuplicates",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/duplicates.rs:32."
          }
        },
        "summary": "`DELETE /database/duplicates` — matches legacy's `?delete=1` param on the same page route",
        "tags": [
          "duplicates"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/duplicates.rs:32",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      },
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/duplicates.rs:169), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nReads every stored group, resolving IDs to current archive metadata and pruning entries that\nhave vanished since the scan (deleted archives) — matches legacy's self-healing behavior in\n`Controller/Duplicates.pm::index` (drops the whole group if it'd shrink below 2 members,\notherwise just removes the vanished ID and rewrites the group).",
        "operationId": "getDuplicates",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/duplicates.rs:32."
          }
        },
        "summary": "Reads every stored group, resolving IDs to current archive metadata and pruning entries that",
        "tags": [
          "duplicates"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/duplicates.rs:32",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/database/duplicates/scan": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/duplicates.rs:47), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nQueues the scan as a background job (matches legacy's `/api/minion/find_duplicates/queue`\nbeing Minion-backed rather than synchronous) since hashing every archive pair is O(n²) and\nshouldn't block the request.",
        "operationId": "scanDuplicates",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/duplicates.rs:36."
          }
        },
        "summary": "Queues the scan as a background job (matches legacy's `/api/minion/find_duplicates/queue`",
        "tags": [
          "duplicates"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/duplicates.rs:36",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/database/import-legacy": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/database.rs:785), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nImports a LANraragi (or LANrurugi) backup JSON file the caller uploads directly — no live\nconnection to any remote Redis instance (see `lanrurugi_backup::import_legacy`'s own module\ndocs for why: the official LANraragi Docker image never exposes its bundled Redis outside the\ncontainer by default). Same multipart-file-upload shape as `queue_restore` above, but this is\ndeliberately its own independent handler/parsing/persistence path, not a variant of\n`queue_restore` — see `import_legacy`'s own docs on why the two must not share code despite\nsuperficially similar JSON shapes.",
        "operationId": "queueImportLegacy",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/database.rs:44."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "summary": "Imports a LANraragi (or LANrurugi) backup JSON file the caller uploads directly — no live",
        "tags": [
          "database"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/database.rs:44",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/database/import-legacy/count": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/database.rs:994), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nHow many times this instance has ever run a LANraragi import — read by the frontend before\nshowing the import UI, so it can warn (\"you've done this before — consider a full backup\nfirst\") on a 2nd-or-later import without waiting for the import itself to complete. Read-only,\nseparate from `queue_import_legacy`'s own increment (which happens after a real import\nfinishes), matching `ImportSnapshotRepository::import_count`'s own read/increment split.",
        "operationId": "importLegacyCount",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/database.rs:48."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "summary": "How many times this instance has ever run a LANraragi import — read by the frontend before",
        "tags": [
          "database"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/database.rs:48",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/database/import-snapshots": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/database.rs:1021), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nNewest-first metadata list (no `document` payload — see\n`ImportSnapshotRepository::list_metadata`'s own docs) for the Backup page's Time-Machine-style\nrollback-point picker.",
        "operationId": "listImportSnapshots",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/database.rs:49."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "summary": "Newest-first metadata list (no `document` payload — see",
        "tags": [
          "database"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/database.rs:49",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/database/import-snapshots/{id}": {
      "delete": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `delete_import_snapshot` in crates/lanrurugi-api/src/database.rs:1063.",
        "operationId": "deleteImportSnapshot",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/database.rs:50."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "summary": "delete_import_snapshot",
        "tags": [
          "database"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/database.rs:50",
        "x-lanrurugi-undocumented": true
      },
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/database.rs:1041), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nReturns the full `BackupDocument` payload as a downloadable JSON file — same shape\n`POST /database/restore` accepts, so the frontend's own \"restore\" flow can take this file\nstraight back in with zero format translation on either side.",
        "operationId": "downloadImportSnapshot",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/database.rs:50."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "summary": "Returns the full `BackupDocument` payload as a downloadable JSON file — same shape",
        "tags": [
          "database"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/database.rs:50",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/database/isnew": {
      "delete": {
        "description": "Clears the \"New!\" flag on all archives.",
        "operationId": "clearNewAll",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Success response"
          }
        },
        "summary": "Clear All \"New\" flags",
        "tags": [
          "database"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/database.rs:41",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/database/rebuild-index": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `rebuild_index` in crates/lanrurugi-api/src/database.rs:702.",
        "operationId": "rebuildIndex",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/database.rs:43."
          }
        },
        "summary": "rebuild_index",
        "tags": [
          "database"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/database.rs:43",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/database/restore": {
      "post": {
        "description": "Queues a Minion job to restore from a backup JSON file.  \nUse the returned job ID to check progress.",
        "operationId": "queueRestoreJob",
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "properties": {
                  "file": {
                    "description": "Backup JSON file to restore",
                    "format": "binary",
                    "type": "string"
                  }
                },
                "required": [
                  "file"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "job": 43,
                    "operation": "queue_restore",
                    "success": 1
                  },
                  "properties": {
                    "job": {
                      "type": "integer"
                    },
                    "operation": {
                      "enum": [
                        "queue_restore"
                      ],
                      "type": "string"
                    },
                    "success": {
                      "enum": [
                        0,
                        1
                      ],
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Enqueued job"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Invalid request"
          }
        },
        "summary": "Queue a restore job",
        "tags": [
          "database"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/database.rs:40",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/database/scripts/repair-mojibake": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/scripts.rs:61), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`POST /database/scripts/repair-mojibake` — see `crate::mojibake`'s own docs for why stored\nrecords can still hold doubly-encoded text long after the importer learned to repair it.",
        "operationId": "repairMojibakeMetadata",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/scripts.rs:41."
          }
        },
        "summary": "`POST /database/scripts/repair-mojibake` — see `crate::mojibake`'s own docs for why stored",
        "tags": [
          "scripts"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/scripts.rs:41",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/database/scripts/subfolders-to-categories": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/scripts.rs:182), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`POST /database/scripts/subfolders-to-categories` — scans the archive folder and creates one\nstatic Category per subfolder that directly contains archives (`FolderToCat.pm::run_script`).\n\nReports its own wall-clock `elapsed_ms` in the response (directory walk through category\ncreation, inclusive) so it can be directly compared against `plugins/script/foldertocat.ts` —\na real `.ts` script plugin doing the identical directory walk + grouping logic itself (via\n`Deno.readDir`, not host-injected paths — the user wanted a genuine head-to-head, not a\ncontrived one where the plugin is handed pre-walked data), used to gauge the real overhead of\nrunning this kind of I/O-heavy whole-library scan through the Deno-subprocess plugin sandbox\nversus native Rust.",
        "operationId": "subfoldersToCategories",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/scripts.rs:33."
          }
        },
        "summary": "`POST /database/scripts/subfolders-to-categories` — scans the archive folder and creates one",
        "tags": [
          "scripts"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/scripts.rs:33",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/database/scripts/subfolders-to-tankoubons": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/scripts.rs:459), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`POST /database/scripts/subfolders-to-tankoubons` — scans only the **first-level subfolders**\nof the archive folder and creates one Tankoubon per such subfolder. All archives under that\nfirst-level subdirectory (including archives in deeper nested subdirectories) are added to the\nsame Tankoubon; nested subdirectories do not create their own Tankoubons. Duplicate names are\nallowed; this is a non-destructive action and never deletes or deduplicates existing\nTankoubons.\n\nWhen a DeepSeek API key is configured, the endpoint best-effort uses the LLM to infer a better\nTankoubon name and an `artist:`/`circle:` author tag. Without a key, or when the LLM call\nfails, it falls back to the raw folder name and skips the author tag.",
        "operationId": "subfoldersToTankoubons",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/scripts.rs:37."
          }
        },
        "summary": "`POST /database/scripts/subfolders-to-tankoubons` — scans only the **first-level subfolders**",
        "tags": [
          "scripts"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/scripts.rs:37",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/database/stats": {
      "get": {
        "description": "Get tags from the database, with a value symbolizing their prevalence.",
        "operationId": "getStatistics",
        "parameters": [
          {
            "description": "Add this parameter if you want to only get tags whose weight is at least the given minimum.  \nDefault is 1 if not specified, to get all tags.",
            "in": "query",
            "name": "minweight",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "description": "Set to true to exclude namespaces configured in server settings from the results. Default behavior returns all tags.",
            "in": "query",
            "name": "hide_excluded_namespaces",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": [
                  {
                    "namespace": "character",
                    "text": "jeanne alter",
                    "weight": 2
                  },
                  {
                    "namespace": "character",
                    "text": "xuanzang",
                    "weight": 2
                  },
                  {
                    "namespace": "parody",
                    "text": "fate grand order",
                    "weight": 3
                  },
                  {
                    "namespace": null,
                    "text": "artbook",
                    "weight": 3
                  }
                ],
                "schema": {
                  "items": {
                    "properties": {
                      "namespace": {
                        "description": "Namespace of the tag",
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "text": {
                        "description": "The tag itself",
                        "type": "string"
                      },
                      "weight": {
                        "description": "Weight of the tag in the database",
                        "type": "integer"
                      }
                    },
                    "required": [
                      "text"
                    ],
                    "type": "object"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Success response"
          }
        },
        "summary": "Get Statistics",
        "tags": [
          "database"
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/database.rs:37"
      }
    },
    "/api/download_queue": {
      "get": {
        "description": "Every item in the download queue, in whatever state it currently holds.\n\nFor live updates prefer `/download_queue/stream`, which pushes state changes as they happen\nrather than requiring a poll.\n",
        "operationId": "listQueue",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "items": {
                      "items": {
                        "description": "One queue item. See the item schema on `/download_queue/{id}`.",
                        "type": "object"
                      },
                      "type": "array"
                    }
                  },
                  "required": [
                    "items"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "The queue."
          }
        },
        "summary": "List queue items",
        "tags": [
          "download_queue"
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/download_queue.rs:30"
      },
      "post": {
        "description": "Bulk add. The caller has already decided which download plugin's `url_pattern` matched each\nURL; this endpoint's only validation is that `plugin_namespace` resolves to an installed\nplugin.\n\nInvalid entries are **rejected individually** rather than failing the whole batch, so one bad\nURL in a pasted list does not block the rest. Check `rejected` in the response.\n",
        "operationId": "addToQueue",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "items": {
                    "items": {
                      "properties": {
                        "auto_fetch_metadata": {
                          "description": "Fetch metadata with the plugin once the download completes.",
                          "type": "boolean"
                        },
                        "category": {
                          "description": "Category to file the downloaded archive under.",
                          "type": "string"
                        },
                        "metadata_tags": {
                          "items": {
                            "type": "string"
                          },
                          "type": "array"
                        },
                        "overwrite_on_duplicate": {
                          "description": "Replace an existing archive with the same identity instead of keeping both.",
                          "type": "boolean"
                        },
                        "plugin_namespace": {
                          "description": "`<category>/<name>`, as shown in the plugin list.",
                          "type": "string"
                        },
                        "url": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "url",
                        "plugin_namespace"
                      ],
                      "type": "object"
                    },
                    "type": "array"
                  }
                },
                "required": [
                  "items"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "added": {
                      "items": {
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "rejected": {
                      "description": "Entries that were refused, each with the reason.",
                      "items": {
                        "properties": {
                          "reason": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "The items that were added, and the ones that were not."
          }
        },
        "summary": "Add items to the queue",
        "tags": [
          "download_queue"
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/download_queue.rs:30",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/download_queue/clear_completed": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/download_queue.rs:3092), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`POST /download_queue/clear_completed` — deletes every `Done` item, returns the count removed.\nDeliberately excludes `Error` — unlike `Done`, an errored item is a restartable, still-\nactionable state (grouped with `Queued`/`Cancelled` everywhere else in this module: selectable,\nhas a retry button, shows its own error text), not \"completed\" work a user is done looking at.\nA user wanting to discard a specific failed item can still do so individually via\n`DELETE /download_queue/{id}` (which does clean up a `pending_filename_conflict`'s own staged\ntemp file — `Done` items never carry one, so this handler has nothing analogous to worry\nabout).",
        "operationId": "clearCompleted",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/download_queue.rs:69."
          }
        },
        "summary": "`POST /download_queue/clear_completed` — deletes every `Done` item, returns the count removed",
        "tags": [
          "download_queue"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/download_queue.rs:69",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/download_queue/delete_selected": {
      "post": {
        "description": "Bulk delete by ID, mirroring `start_selected`'s semantics: an ID that is missing or already\ndeleted is a **silent no-op**, not an error, so a stale selection can be passed safely.\n\nAn ID that is currently downloading is likewise a silent no-op — the same safety rule the\nsingle-item delete enforces with a 409.\n",
        "operationId": "deleteSelected",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "ids": {
                    "items": {
                      "type": "string"
                    },
                    "type": "array"
                  }
                },
                "required": [
                  "ids"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "deleted": {
                      "items": {
                        "type": "string"
                      },
                      "type": "array"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "The IDs that were actually removed."
          }
        },
        "summary": "Delete selected queue items",
        "tags": [
          "download_queue"
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/download_queue.rs:68",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/download_queue/start_all": {
      "post": {
        "description": "Starts every item in the `queued` state, concurrently. Each spawns its own background task;\nper-domain concurrency and rate limiting are enforced downstream by the download manager, so\nno extra throttling happens here.\n\nDeliberately narrower than the single-item start: items in `error` or `cancelled` are retried\none at a time on purpose, and an item parked awaiting a version-conflict answer is **never**\nswept up — bulk-starting it would answer, by omission, the very question it is parked on.\n",
        "operationId": "startAll",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "skipped": {
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "reason": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "started": {
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "job": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Which items started, and which were skipped and why."
          }
        },
        "summary": "Start every queued item",
        "tags": [
          "download_queue"
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/download_queue.rs:66",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/download_queue/start_selected": {
      "post": {
        "description": "Like `start_all`, filtered to the IDs given.\n\nAn ID that is already in flight, already finished, or otherwise not startable is a **silent\nno-op**, not an error — so a client can pass a selection without re-checking each item's\ncurrent state first.\n",
        "operationId": "startSelected",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "ids": {
                    "items": {
                      "type": "string"
                    },
                    "type": "array"
                  }
                },
                "required": [
                  "ids"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "skipped": {
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "reason": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "started": {
                      "items": {
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "job": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Which items started, and which were skipped and why."
          }
        },
        "summary": "Start selected queue items",
        "tags": [
          "download_queue"
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/download_queue.rs:67",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/download_queue/stream": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/download_queue.rs:75), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`GET /download_queue/stream` — SSE endpoint that pushes incremental queue-item state changes\nbroadcast by `update_queue_item_state` after each successful Redis write. The first event is\n`full` with the current complete list so the client can bootstrap without a separate HTTP GET.",
        "operationId": "queueStream",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/download_queue.rs:31."
          }
        },
        "summary": "`GET /download_queue/stream` — SSE endpoint that pushes incremental queue-item state changes",
        "tags": [
          "download_queue"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/download_queue.rs:31",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/download_queue/{id}": {
      "delete": {
        "description": "Removes one item from the queue.\n\nAn item that is currently downloading is refused with a 409 — deleting its record out from\nunder the running background task would leave that task updating nothing, with no queue row\nleft to show progress and no way to stop the transfer. Stop it first.\n\nA queue item that has already been ingested into the library is also refused; remove the\nresulting archive instead.\n",
        "operationId": "deleteQueueItem",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Deleted."
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "The item is currently downloading; stop it before deleting."
          }
        },
        "summary": "Delete a queue item",
        "tags": [
          "download_queue"
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/download_queue.rs:32",
        "x-lanrurugi-token-role": "admin"
      },
      "patch": {
        "description": "Partial update: a field omitted from the request body keeps its stored value rather than being\ncleared. Same convention as `PUT /plugins/options`.\n",
        "operationId": "updateQueueItem",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "description": "Any subset of the item's editable fields.",
                "properties": {
                  "category": {
                    "type": "string"
                  },
                  "metadata_tags": {
                    "items": {
                      "type": "string"
                    },
                    "type": "array"
                  }
                },
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Updated."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "No such queue item."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "summary": "Update a queue item",
        "tags": [
          "download_queue"
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/download_queue.rs:32"
      }
    },
    "/api/download_queue/{id}/compare": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/download_queue.rs:1857), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`POST /download_queue/{id}/compare` — issue #77's AI quality-comparison judgment for a\n`PendingFilenameConflict`: page-aligns and sharpness-compares the freshly-staged download\n(`temp_path`, exposed to the response as [`imgcompare::Side::A`]) against the already-cataloged\narchive that owns the colliding filename (`existing_id`'s own file, exposed as\n[`imgcompare::Side::B`]) — read-only, resolves nothing on its own; the frontend still calls\n`.../overwrite` or `.../rename` afterward based on what the user (with or without this\ncomparison's help) decides. `A`/`B` are deliberately not labeled \"new\"/\"old\" in the response\nitself (see [`lanrurugi_imgcompare::Side`]'s own docs) — this handler is what actually assigns\nthat meaning for this specific caller.\n\nKept alongside the streaming [`compare_queue_item_stream`] below (not replaced by it) as a\nplain request/response fallback for any caller that doesn't want SSE — the frontend's own\n`ComparisonResultModal` flow now goes through the stream endpoint instead (see that handler's\nown docs for why).",
        "operationId": "compareQueueItem",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/download_queue.rs:44."
          }
        },
        "summary": "`POST /download_queue/{id}/compare` — issue #77's AI quality-comparison judgment for a",
        "tags": [
          "download_queue"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/download_queue.rs:44",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/download_queue/{id}/compare/export-patch": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/download_queue.rs:2604), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`POST /download_queue/{id}/compare/export-patch` — builds a `.patch.zip` from a user-picked\nselection of A's own unique pages (`ComparisonResult.a_unmatched_pages`, issue #77's own\nfollow-on design) and returns its raw bytes for download, ready to be dropped next to the\ntarget archive on disk as `<archive filename>.patch.zip` (`lanrurugi_scanner::patch`) — this\nendpoint doesn't write it there itself (confirmed design: \"补丁不需要应用，只要放在漫画目录内\"\n— a user-placed file, not one this server installs unprompted). The target is always B (the\nexisting library archive this conflict is against) — a patch only ever adds A's own extra\npages on top of the version the user is keeping, never the other way around.",
        "operationId": "exportComparePatch",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/download_queue.rs:61."
          }
        },
        "summary": "`POST /download_queue/{id}/compare/export-patch` — builds a `.patch.zip` from a user-picked",
        "tags": [
          "download_queue"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/download_queue.rs:61",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/download_queue/{id}/compare/keep-b": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/download_queue.rs:2713), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`POST /download_queue/{id}/compare/keep-b` — the \"keep the existing library archive, discard\nthis download\" resolution (issue #77's own follow-on design). Distinct from every other\nresolve action in this file: those all catalogue the *staged* file (A) as some archive, either\nreplacing B (`overwrite_queue_item`) or coexisting with it (`rename_queue_item`) — this one\ncatalogues nothing at all. B is already in the library and stays exactly where it is; the only\ntwo things that happen are (1) `insertions`, if any, get written straight to B's own sidecar\n`.patch.zip` (not just returned for the user to place themselves, unlike\n[`export_compare_patch`] — this flow's own frontend already walked the user through picking\ninsertion points as part of *choosing* to keep B, so there's no separate \"now go place this\nfile\" step left for them to do), and (2) the queue item is deleted outright, same as a plain\n`DELETE /download_queue/{id}` — there's nothing left for it to track (confirmed design: a\npatch's own existence lives on disk next to B, not as a queue-item flag).",
        "operationId": "keepSideB",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/download_queue.rs:65."
          }
        },
        "summary": "`POST /download_queue/{id}/compare/keep-b` — the \"keep the existing library archive, discard",
        "tags": [
          "download_queue"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/download_queue.rs:65",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/download_queue/{id}/compare/page": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/download_queue.rs:2348), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`GET /download_queue/{id}/compare/page?side=a|b&index=N` — the actual page image behind one\n`PageComparison` sample from `.../compare`'s own response, for the frontend's\noverlay/shift-to-toggle comparison view. `side=a` reads from the still-uncatalogued staged\ndownload (`temp_path` — no archive ID exists yet, so this can't reuse `archives.rs`'s own\n`GET /archives/{id}/page`); `side=b` reads from the existing archive's own file. `index` is a\nnatural-sort page position (matching `lanrurugi_imgcompare::PageComparison`'s own\n`a_page_index`/`b_page_index`), resolved to an entry name via `list_pages` fresh on every call\nrather than cached — this endpoint is only ever hit a handful of times per conflict (issue #77's\nown confirmed sample size of 3-5), so re-listing isn't worth adding a cache for.",
        "operationId": "compareQueueItemPage",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/download_queue.rs:53."
          }
        },
        "summary": "`GET /download_queue/{id}/compare/page?side=a|b&index=N` — the actual page image behind one",
        "tags": [
          "download_queue"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/download_queue.rs:53",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/download_queue/{id}/compare/pages": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/download_queue.rs:2476), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`GET /download_queue/{id}/compare/pages?side=a|b` — the real entry-name list for one side of\nthe comparison, in natural-sort (page) order. The frontend's own \"pick where to insert an\nunmatched A page\" UI (issue #77's own follow-on design) needs B's real entry names to send as\n`after_filename`/`before_filename` in `.../export-patch` — it otherwise only ever deals in\nnumeric page indices (matching `PageComparison.a_page_index`/`b_page_index`), which\n`patch.rs`'s own JSON schema deliberately doesn't use (see that module's docs on why an entry\nname is the stable anchor, not an index that can drift if the archive is re-scanned).",
        "operationId": "listComparePages",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/download_queue.rs:57."
          }
        },
        "summary": "`GET /download_queue/{id}/compare/pages?side=a|b` — the real entry-name list for one side of",
        "tags": [
          "download_queue"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/download_queue.rs:57",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/download_queue/{id}/compare/stream": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/download_queue.rs:1988), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`GET /download_queue/{id}/compare/stream` — the SSE-streaming twin of [`compare_queue_item`]\nabove, per issue #77's own confirmed two-phase design (see\n`lanrurugi_imgcompare::compare_archives_streaming`'s own docs for the full phase-1/phase-2\nbreakdown): opens an `EventSource`-compatible stream (GET, not POST — `EventSource` can't send\na request body or use any other HTTP method) that emits one `event: sample` per\n`CompareEvent::Sample` (coarse first, precise replacements streamed in afterward) and a single\nterminal `event: done` carrying the rest of `ComparisonResult`'s own fields — never relies on\nconnection-close/timeout as an implicit end signal (\"并且sse要有结束标记\"). The frontend opens\nits result view the moment the FIRST `sample` event arrives rather than waiting for the whole\ncomparison, which for a ~200-page archive pair is otherwise several seconds of dead time\n(\"假设有6对结果（粗加工），可以用sse分6次发，发第一对的时候就开始显示modal，这样是一个很大的提速\").\n\nA cache hit (same policy as `compare_queue_item`'s own) is replayed as a single burst of\n`sample` events (`phase: coarse` for all of them — a cached result has already gone through\nboth phases, and the frontend only cares about \"is this a display-final value\", which `Coarse`\nvs `Precise` doesn't actually change once the value itself is final) immediately followed by\n`done`, so re-opening an already-computed comparison is still effectively instant.",
        "operationId": "compareQueueItemStream",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/download_queue.rs:49."
          }
        },
        "summary": "`GET /download_queue/{id}/compare/stream` — the SSE-streaming twin of [`compare_queue_item`]",
        "tags": [
          "download_queue"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/download_queue.rs:49",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/download_queue/{id}/confirm-older": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/download_queue.rs:707), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`POST /download_queue/{id}/confirm-older` — answers the\n`AwaitingRevisionConfirmation` prompt raised when a download turned out to be an *older*\nrevision than an archive already in the library (issue #107's `warn_then_conflict_menu`).\n\n`proceed: true` records the decision on the item and restarts it — the restart runs straight\nthrough the same version-history check instead of parking again, because `revision_confirmed`\nis now set. `proceed: false` moves the item to `Cancelled`, which is already the \"user stopped\nthis on purpose, still restartable\" state, so no new terminal state is needed.\n\nDeliberately separate from `.../start`: `AwaitingRevisionConfirmation` is not `is_startable`,\nso the ordinary Start button can't skip the question by accident.",
        "operationId": "confirmOlderRevision",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/download_queue.rs:45."
          }
        },
        "summary": "`POST /download_queue/{id}/confirm-older` — answers the",
        "tags": [
          "download_queue"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/download_queue.rs:45",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/download_queue/{id}/fetch-metadata": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/download_queue.rs:148), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`POST /download_queue/{id}/fetch-metadata` — the \"Fetch Metadata\" button's backend path. Runs\nthe same plugin-`execMetadata` + `metadata_preview`-cache logic the post-download auto-fetch\nuses (`plugins::ensure_metadata_cached`), so a manual preview and the automatic one share one\ncode path (and one 10-min TTL) instead of the frontend calling `POST /plugins/use` itself.",
        "operationId": "fetchQueueItemMetadata",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/download_queue.rs:36."
          }
        },
        "summary": "`POST /download_queue/{id}/fetch-metadata` — the \"Fetch Metadata\" button's backend path",
        "tags": [
          "download_queue"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/download_queue.rs:36",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/download_queue/{id}/overwrite": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/download_queue.rs:1362), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`POST /download_queue/{id}/overwrite` — resolves a `PendingFilenameConflict` by deleting the\nexisting archive (B) and cataloguing the staged download (A) under its originally-intended\nfilename.\n\n`body.insertions`, when given, packages B's own unique pages into a `.patch.zip` for the *new*\nA archive (issue #77's own follow-on design: even when A wins overall, some of B's pages might\nstill be worth keeping) — this has to read B's pages and resolve B's own category/tag/progress\nmetadata *before* calling `resolve_conflict`, since that call deletes B's file and record as\npart of cataloguing A (`pipeline.rs::delete_existing_archive`) — there's nothing left to read\nfrom afterward.",
        "operationId": "overwriteQueueItem",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/download_queue.rs:42."
          }
        },
        "summary": "`POST /download_queue/{id}/overwrite` — resolves a `PendingFilenameConflict` by deleting the",
        "tags": [
          "download_queue"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/download_queue.rs:42",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/download_queue/{id}/rename": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/download_queue.rs:1649), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`POST /download_queue/{id}/rename` — resolves a `PendingFilenameConflict` by cataloguing the\nstaged download under `body.filename` instead, leaving the existing archive that owns the\noriginal filename untouched (a fresh, separate, coexisting archive). If `filename` itself also\ncollides with some other archive, the item ends up with a *new* `PendingFilenameConflict`\n(still resolvable, just against a different existing archive/filename) rather than losing the\nalready-downloaded bytes — see `ingest::resolve_rename`'s own docs.",
        "operationId": "renameQueueItem",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/download_queue.rs:43."
          }
        },
        "summary": "`POST /download_queue/{id}/rename` — resolves a `PendingFilenameConflict` by cataloguing the",
        "tags": [
          "download_queue"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/download_queue.rs:43",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/download_queue/{id}/start": {
      "post": {
        "description": "Resolves the item's own download plugin (chosen when it was added, not now), marks it as\nstarting, and launches the real download in the background. The item's `state`, `job_id` and\n`error` stay current as the transfer proceeds.\n",
        "operationId": "startQueueItem",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Started; the body carries the job id."
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "The item is not in a startable state."
          }
        },
        "summary": "Start one queue item",
        "tags": [
          "download_queue"
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/download_queue.rs:40",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/download_queue/{id}/stop": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/download_queue.rs:1253), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`POST /download_queue/{id}/stop` — cancels an in-flight (`Starting`/`Downloading`) item's\ndownload and reverts it to `Queued` so it can be restarted. Cooperative, not forceful: this\nonly signals `AppState::download_cancellations`' token for the item — the background task\nitself notices at its next `tokio::select!` poll (see `download_manager::stream::download_one`)\nand does its own partial-file cleanup before actually finishing, at which point `start_download`\nreverts the queue item's state (see the `cancel_for_task.is_cancelled()` branch there). No\ntoken present means either the item was never actually downloading (nothing to stop) or it\nalready finished on its own just before this request landed — both are `NotRunning`, not an\nerror worth surfacing loudly, since the desired end state (\"this item isn't downloading\") is\nalready true either way.",
        "operationId": "stopQueueItem",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/download_queue.rs:41."
          }
        },
        "summary": "`POST /download_queue/{id}/stop` — cancels an in-flight (`Starting`/`Downloading`) item's",
        "tags": [
          "download_queue"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/download_queue.rs:41",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/download_url": {
      "post": {
        "description": "Add a URL to be downloaded by the server and added to its library.",
        "operationId": "downloadUrl",
        "parameters": [
          {
            "description": "URL to download",
            "in": "query",
            "name": "url",
            "required": false,
            "schema": {
              "format": "uri",
              "type": "string"
            }
          },
          {
            "description": "Category ID to add the downloaded URL to.",
            "in": "query",
            "name": "catid",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/CategoryId"
            }
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "properties": {
                  "catid": {
                    "$ref": "#/components/schemas/CategoryId",
                    "description": "Category ID to add the downloaded URL to."
                  },
                  "url": {
                    "description": "URL to download",
                    "format": "uri",
                    "type": "string"
                  }
                },
                "required": [
                  "url"
                ],
                "type": "object"
              }
            }
          },
          "required": false
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "job": 86,
                    "operation": "download_url",
                    "success": 1,
                    "url": "https://example.com"
                  },
                  "properties": {
                    "category": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "job": {
                      "type": "integer"
                    },
                    "operation": {
                      "enum": [
                        "download_url"
                      ],
                      "type": "string"
                    },
                    "success": {
                      "enum": [
                        0,
                        1
                      ],
                      "type": "integer"
                    },
                    "url": {
                      "description": "URL queued for download",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Enqueued job"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Bad request"
          }
        },
        "summary": "Queue a URL download",
        "tags": [
          "misc"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/plugins.rs:433",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/info": {
      "get": {
        "description": "Returns some basic information about the LRR instance this server is running.",
        "operationId": "getServerInfo",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServerInfo"
                }
              }
            },
            "description": "Server info"
          }
        },
        "security": [],
        "summary": "Get server info",
        "tags": [
          "misc"
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/misc.rs:41"
      }
    },
    "/api/jobs": {
      "delete": {
        "description": "Removes jobs that have reached a terminal state — `finished` **or** `failed`. Jobs still in\nflight (`queued`, `active`) are left tracked; use the single-job endpoint once they settle.\n\nOnly `state=finished` is accepted. The name means \"any terminal-state job\", which is why\n`failed` jobs are swept up too.\n",
        "operationId": "clearFinishedJobs",
        "parameters": [
          {
            "description": "Only `finished` is accepted; a missing or different value is a 400.",
            "in": "query",
            "name": "state",
            "required": true,
            "schema": {
              "enum": [
                "finished"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/OperationResponse"
                    },
                    {
                      "properties": {
                        "cleared": {
                          "description": "Number of terminal-state jobs removed.",
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    }
                  ]
                }
              }
            },
            "description": "How many jobs were cleared."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "`state` was missing, or was something other than `finished`."
          }
        },
        "summary": "Clear every finished job",
        "tags": [
          "jobs"
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/jobs.rs:36",
        "x-lanrurugi-token-role": "admin"
      },
      "get": {
        "description": "Every job this instance is tracking, most recently created first.\n\nProgress and byte counts are best-effort: a job that cannot report granular progress jumps\nstraight from 0 to 1 rather than reporting a fabricated intermediate value, and the byte\nfields are genuinely absent (not `0`) for jobs that have no transfer to report.\n",
        "operationId": "listJobs",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "jobs": {
                      "items": {
                        "$ref": "#/components/schemas/JobStatus"
                      },
                      "type": "array"
                    }
                  },
                  "required": [
                    "jobs"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "The current job list."
          }
        },
        "summary": "List tracked background jobs",
        "tags": [
          "jobs"
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/jobs.rs:36"
      }
    },
    "/api/jobs/{id}": {
      "delete": {
        "description": "Clears a single job by ID, and only once it has reached a terminal state.\n\nA job that is still `queued` or `active` is a 409 rather than being silently killed — a job\nthat reports success while its work is still running would be worse than an error.\n",
        "operationId": "clearJob",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Cleared."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "No job with that ID is tracked."
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "The job is still queued or active; wait for it to finish or fail first."
          }
        },
        "summary": "Clear one job",
        "tags": [
          "jobs"
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/jobs.rs:37",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/llm/key-status": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/misc.rs:115), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`GET /api/llm/key-status` — returns `{ configured: true/false }`.",
        "operationId": "llmKeyStatus",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/misc.rs:20."
          }
        },
        "summary": "`GET /api/llm/key-status` — returns `{ configured: true/false }`",
        "tags": [
          "misc"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/misc.rs:20",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/login": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `login` in crates/lanrurugi-api/src/login.rs:209.",
        "operationId": "login",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/login.rs:37."
          }
        },
        "security": [],
        "summary": "login",
        "tags": [
          "login"
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/login.rs:37",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/login/status": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/login.rs:619), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`GET /login/status` — reports whether the caller is \"logged in\" for the purposes of gating\nadmin-only UI, matching legacy's own `userlogged` template variable\n(`Controller/Reader.pm`/`Index.pm`: `enable_pass == 0 || session('is_logged')`). Deliberately\nits own endpoint rather than a new field on `/info` — `/info` mirrors legacy's third-party\n`ServerInfo` OpenAPI schema field-for-field (constitution Principle II), and `logged_in` has no\nplace in that contract.",
        "operationId": "status",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/login.rs:38."
          }
        },
        "security": [],
        "summary": "`GET /login/status` — reports whether the caller is \"logged in\" for the purposes of gating",
        "tags": [
          "login"
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/login.rs:38",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/logout": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/login.rs:671), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nNow that refresh tokens are stateful (`lanrurugi_storage::refresh_tokens`), logout actually\nrevokes — burns the entire refresh-token family the caller's access token was minted from\n(same remediation `rotate`'s own reuse-detection path takes), not just clearing the browser's\ncookies and hoping the now-orphaned access token quietly expires on its own within the next\nfew hours.\n\nReads `fid` out of the *access* token (`session::COOKIE_NAME`, `Path=/`), not the refresh\ncookie — deliberately, so `REFRESH_COOKIE_PATH` can stay scoped to only\n`POST /api/token/refresh` without also needing to cover this endpoint. Uses\n`family_id_ignoring_expiry` rather than the normal expiry-checking verifier: a user who clicks\n\"log out\" after their access token has already expired (idle tab, hasn't hit any endpoint that\nwould have triggered a silent refresh yet) must still get their refresh-token family actually\nrevoked — an already-expired access token still proves genuine origin via its signature, which\nis all this needs.",
        "operationId": "logout",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/login.rs:39."
          }
        },
        "security": [],
        "summary": "Now that refresh tokens are stateful (`lanrurugi_storage::refresh_tokens`), logout actually",
        "tags": [
          "login"
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/login.rs:39",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/logs/{category}": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `get_log_lines` in crates/lanrurugi-api/src/logs.rs:26.",
        "operationId": "getLogLines",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/logs.rs:18."
          }
        },
        "summary": "get_log_lines",
        "tags": [
          "logs"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/logs.rs:18",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/minion/{jobid}": {
      "get": {
        "description": "For a given Minion job ID, check whether it succeeded or failed.  \nMinion jobs are ran for various occasions like thumbnails, cache warmup and handling incoming files.  \nFor some jobs, you can check the notes field for progress information. Look at https://docs.mojolicious.org/Minion/Guide#Job-progress for more information.",
        "operationId": "minionJobStatus",
        "parameters": [
          {
            "description": "ID of the Job to query status for.",
            "in": "path",
            "name": "jobid",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "error": null,
                    "notes": {
                      "example_note": "This note could contain the name of the upload, for example."
                    },
                    "state": "finished",
                    "task": "handle_upload"
                  },
                  "properties": {
                    "error": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "notes": {
                      "description": "Arbitrary data attached to the Job.",
                      "type": [
                        "object",
                        "null"
                      ]
                    },
                    "state": {
                      "enum": [
                        "inactive",
                        "active",
                        "finished",
                        "failed"
                      ],
                      "type": "string"
                    },
                    "task": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Job status data"
          }
        },
        "summary": "Get the basic status of a Minion Job",
        "tags": [
          "minion"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/jobs.rs:32"
      }
    },
    "/api/minion/{jobid}/detail": {
      "get": {
        "description": "Get the status of a Minion Job.  \nThis API is there for internal usage mostly, but you can use it to get detailed status for jobs like plugin runs or URL downloads.",
        "operationId": "minionJobDetail",
        "parameters": [
          {
            "in": "path",
            "name": "jobid",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "args": [
                      "/tmp/QF3UCnKdMr/myfile.zip"
                    ],
                    "attempts": 1,
                    "children": [],
                    "created": 1601145004,
                    "delayed": 1601145004,
                    "expires": null,
                    "finished": 1601145004,
                    "id": 7,
                    "lax": 0,
                    "notes": {},
                    "parents": [],
                    "priority": 0,
                    "queue": "default",
                    "result": {
                      "id": "75d18ce470dc99f83dc355bdad66319d1f33c82b",
                      "message": "This file already exists in the Library.",
                      "success": 0
                    },
                    "retried": null,
                    "retries": 0,
                    "started": 1601145004,
                    "state": "finished",
                    "task": "handle_upload",
                    "time": 1601145005,
                    "worker": 1
                  },
                  "type": "object"
                }
              }
            },
            "description": "Job detail"
          }
        },
        "summary": "Get the full status of a Minion Job",
        "tags": [
          "minion"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/jobs.rs:33"
      }
    },
    "/api/minion/{jobname}/queue": {
      "post": {
        "description": "Queue a Job with the specified type and parameters.  \nSee LANraragi::Utils::Minion for all the available types of jobs and the parameters they require.  \n\nThere's no API contract in place for whether a Job type exists on a given server version, so I wouldn't recommend using this unless you have a good reason to.  ",
        "operationId": "queueMinionJob",
        "parameters": [
          {
            "description": "Type of the job to instantiate.",
            "in": "path",
            "name": "jobname",
            "required": true,
            "schema": {
              "enum": [
                "thumbnail_task",
                "tank_thumbnail_task",
                "page_thumbnails",
                "regen_all_thumbnails",
                "find_duplicates",
                "build_stat_hashes",
                "handle_upload",
                "download_url",
                "run_plugin"
              ],
              "type": "string"
            }
          },
          {
            "description": "Arguments as a JSON array string",
            "in": "query",
            "name": "args",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Priority of the Minion job. The higher the number, the more important the job is.",
            "in": "query",
            "name": "priority",
            "required": false,
            "schema": {
              "default": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MinionJobResponse"
                }
              }
            },
            "description": "Enqueued job"
          }
        },
        "summary": "Queue a Minion job",
        "tags": [
          "minion"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/jobs.rs:34",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/opds": {
      "get": {
        "description": "Get the Archive Index as an OPDS 1.2 Catalog with PSE 1.1 compatibility.",
        "operationId": "getOpdsCatalog",
        "parameters": [
          {
            "description": "Category ID. If passed, the OPDS catalog will be filtered to only show archives from this category.",
            "in": "query",
            "name": "category",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/xml": {
                "examples": {
                  "xml": {
                    "summary": "A sample XML response",
                    "value": "<?xml version=\"1.0\" encoding=\"UTF-8\"?> <feed xmlns=\"http://www.w3.org/2005/Atom\" \n    xmlns:dcterms=\"http://purl.org/dc/terms/\"\n    xmlns:opds=\"http://opds-spec.org/2010/catalog\" \n    xmlns:pse=\"http://vaemendis.net/opds-pse/ns\">\n\n    <id>urn:lrr:0</id>\n\n    <link rel=\"self\" href=\"/api/opds\" type=\"application/atom+xml;profile=opds-catalog;kind=acquisition\" />\n    <link rel=\"start\" href=\"/api/opds\" type=\"application/atom+xml;profile=opds-catalog;kind=acquisition\" />\n\n    <title>LANraragi</title>\n    <updated>2010-01-10T10:03:10Z</updated>\n    <subtitle>Welcome to this Library running LANraragi!</subtitle>\n    <icon>/favicon.ico</icon>\n    <author>\n        <name>9.9.9</name>\n        <uri>http://github.org/Difegue/LANraragi</uri>\n    </author>\n\n    <link rel=\"http://opds-spec.org/facet\" \n        href=\"/api/opds\" \n        title=\"All Archives\" \n        opds:facetGroup=\"Categories\" \n        opds:activeFacet=\"true\" />\n\n    \n    <link rel=\"http://opds-spec.org/facet\" \n        href=\"/api/opds?category=SET_1589138380\" \n        title=\"AMERICA ONRY\"  \n        opds:facetGroup=\"Categories\" \n        />\n    \n    <link rel=\"http://opds-spec.org/facet\" \n        href=\"/api/opds?category=SET_1589141306\" \n        title=\"Segata Sanshiro\"  \n        opds:facetGroup=\"Categories\" \n        thr:count=\"2\" />\n    \n\n    \n    <entry>\n        <title>Fate GO MEMO</title>\n        <id>urn:lrr:28697b96f0ac5858be2614ed10ca47742c9522fd</id>\n        <updated>2010-01-10T10:01:11Z</updated>\n        <published>2010-01-10T10:01:11Z</published>\n        <author>\n            <name>wada rco</name>\n        </author>\n        <rights></rights>\n        <dcterms:language></dcterms:language>\n        <dcterms:publisher>wadamemo</dcterms:publisher>\n        <dcterms:issued></dcterms:issued>\n        \n        <category term=\"Archive\" />\n        \n        <summary>parody:fate grand order,  group:wadamemo,  artist:wada rco,  artbook,  full color, male:very cool too</summary>\n\n        <link rel=\"alternate\" href=\"/api/opds/28697b96f0ac5858be2614ed10ca47742c9522fd\"\n            type=\"application/atom+xml;type=entry;profile=opds-catalog\" />\n\n        <link rel=\"http://opds-spec.org/image\" href=\"/api/archives/28697b96f0ac5858be2614ed10ca47742c9522fd/thumbnail\" type=\"image/jpeg\" />\n        <link rel=\"http://opds-spec.org/image/thumbnail\" href=\"/api/archives/28697b96f0ac5858be2614ed10ca47742c9522fd/thumbnail\"\n            type=\"image/jpeg\" />\n        <link rel=\"http://opds-spec.org/acquisition\" href=\"/api/archives/28697b96f0ac5858be2614ed10ca47742c9522fd/download\" title=\"Download/Read\"\n            type=\"application/x-cbz\" />\n        <link rel=\"http://vaemendis.net/opds-pse/stream\" type=\"image/jpeg\"\n            href=\"/api/opds/28697b96f0ac5858be2614ed10ca47742c9522fd/pse?page={pageNumber}\" pse:count=\"1\" />\n        <link type=\"text/html\" rel=\"alternate\" title=\"Open in LANraragi\" href=\"/reader?id=28697b96f0ac5858be2614ed10ca47742c9522fd\" />\n    </entry>\n    \n    <entry>\n        <title>Fate GO MEMO 2</title>\n        <id>urn:lrr:2810d5e0a8d027ecefebca6237031a0fa7b91eb3</id>\n        <updated>2010-01-10T10:01:11Z</updated>\n        <published>2010-01-10T10:01:11Z</published>\n        <author>\n            <name>wada rco</name>\n        </author>\n        <rights></rights>\n        <dcterms:language></dcterms:language>\n        <dcterms:publisher>wadamemo</dcterms:publisher>\n        <dcterms:issued></dcterms:issued>\n        \n        <category term=\"Archive\" />\n        \n        <summary>parody:fate grand order,  character:abigail williams,  character:artoria pendragon alter,  character:asterios,  character:ereshkigal,  character:gilgamesh,  character:hans christian andersen,  character:hassan of serenity,  character:hector,  character:helena blavatsky,  character:irisviel von einzbern,  character:jeanne alter,  character:jeanne darc,  character:kiara sessyoin,  character:kiyohime,  character:lancer,  character:martha,  character:minamoto no raikou,  character:mochizuki chiyome,  character:mordred pendragon,  character:nitocris,  character:oda nobunaga,  character:osakabehime,  character:penthesilea,  character:queen of sheba,  character:rin tosaka,  character:saber,  character:sakata kintoki,  character:scheherazade,  character:sherlock holmes,  character:suzuka gozen,  character:tamamo no mae,  character:ushiwakamaru,  character:waver velvet,  character:xuanzang,  character:zhuge liang,  group:wadamemo,  artist:wada rco,  artbook,  full color</summary>\n\n        <link rel=\"alternate\" href=\"/api/opds/2810d5e0a8d027ecefebca6237031a0fa7b91eb3\"\n            type=\"application/atom+xml;type=entry;profile=opds-catalog\" />\n\n        <link rel=\"http://opds-spec.org/image\" href=\"/api/archives/2810d5e0a8d027ecefebca6237031a0fa7b91eb3/thumbnail\" type=\"image/jpeg\" />\n        <link rel=\"http://opds-spec.org/image/thumbnail\" href=\"/api/archives/2810d5e0a8d027ecefebca6237031a0fa7b91eb3/thumbnail\"\n            type=\"image/jpeg\" />\n        <link rel=\"http://opds-spec.org/acquisition\" href=\"/api/archives/2810d5e0a8d027ecefebca6237031a0fa7b91eb3/download\" title=\"Download/Read\"\n            type=\"application/x-cbz\" />\n        <link rel=\"http://vaemendis.net/opds-pse/stream\" type=\"image/jpeg\"\n            href=\"/api/opds/2810d5e0a8d027ecefebca6237031a0fa7b91eb3/pse?page={pageNumber}\" pse:count=\"34\"  pse:lastRead=\"34\" />\n        <link type=\"text/html\" rel=\"alternate\" title=\"Open in LANraragi\" href=\"/reader?id=2810d5e0a8d027ecefebca6237031a0fa7b91eb3\" />\n    </entry>\n    \n    <entry>\n        <title>Ghost in the Shell 1.5 - Human-Error Processor vol01ch01</title>\n        <id>urn:lrr:4857fd2e7c00db8b0af0337b94055d8445118630</id>\n        <updated>2010-01-10T10:01:11Z</updated>\n        <published>2010-01-10T10:01:11Z</published>\n        <author>\n            <name>shirow masamune</name>\n        </author>\n        <rights></rights>\n        <dcterms:language></dcterms:language>\n        <dcterms:publisher></dcterms:publisher>\n        <dcterms:issued></dcterms:issued>\n        \n        <category term=\"Archive\" />\n        \n        <summary>artist:shirow masamune</summary>\n\n        <link rel=\"alternate\" href=\"/api/opds/4857fd2e7c00db8b0af0337b94055d8445118630\"\n            type=\"application/atom+xml;type=entry;profile=opds-catalog\" />\n\n        <link rel=\"http://opds-spec.org/image\" href=\"/api/archives/4857fd2e7c00db8b0af0337b94055d8445118630/thumbnail\" type=\"image/jpeg\" />\n        <link rel=\"http://opds-spec.org/image/thumbnail\" href=\"/api/archives/4857fd2e7c00db8b0af0337b94055d8445118630/thumbnail\"\n            type=\"image/jpeg\" />\n        <link rel=\"http://opds-spec.org/acquisition\" href=\"/api/archives/4857fd2e7c00db8b0af0337b94055d8445118630/download\" title=\"Download/Read\"\n            type=\"application/x-cbz\" />\n        <link rel=\"http://vaemendis.net/opds-pse/stream\" type=\"image/jpeg\"\n            href=\"/api/opds/4857fd2e7c00db8b0af0337b94055d8445118630/pse?page={pageNumber}\" pse:count=\"34\"  pse:lastRead=\"34\" />\n        <link type=\"text/html\" rel=\"alternate\" title=\"Open in LANraragi\" href=\"/reader?id=4857fd2e7c00db8b0af0337b94055d8445118630\" />\n    </entry>\n    \n    <entry>\n        <title>Rohan Kishibe goes to Gucci</title>\n        <id>urn:lrr:e4c422fd10943dc169e3489a38cdbf57101a5f7e</id>\n        <updated>2010-01-10T10:01:11Z</updated>\n        <published>2010-01-10T10:01:11Z</published>\n        <author>\n            <name></name>\n        </author>\n        <rights></rights>\n        <dcterms:language></dcterms:language>\n        <dcterms:publisher></dcterms:publisher>\n        <dcterms:issued></dcterms:issued>\n        \n        <category term=\"New Archive\" />\n        \n        <summary>parody: jojo&#39;s bizarre adventure</summary>\n\n        <link rel=\"alternate\" href=\"/api/opds/e4c422fd10943dc169e3489a38cdbf57101a5f7e\"\n            type=\"application/atom+xml;type=entry;profile=opds-catalog\" />\n\n        <link rel=\"http://opds-spec.org/image\" href=\"/api/archives/e4c422fd10943dc169e3489a38cdbf57101a5f7e/thumbnail\" type=\"image/jpeg\" />\n        <link rel=\"http://opds-spec.org/image/thumbnail\" href=\"/api/archives/e4c422fd10943dc169e3489a38cdbf57101a5f7e/thumbnail\"\n            type=\"image/jpeg\" />\n        <link rel=\"http://opds-spec.org/acquisition\" href=\"/api/archives/e4c422fd10943dc169e3489a38cdbf57101a5f7e/download\" title=\"Download/Read\"\n            type=\"application/x-cbz\" />\n        <link rel=\"http://vaemendis.net/opds-pse/stream\" type=\"image/jpeg\"\n            href=\"/api/opds/e4c422fd10943dc169e3489a38cdbf57101a5f7e/pse?page={pageNumber}\" pse:count=\"10\" />\n        <link type=\"text/html\" rel=\"alternate\" title=\"Open in LANraragi\" href=\"/reader?id=e4c422fd10943dc169e3489a38cdbf57101a5f7e\" />\n    </entry>\n    \n    <entry>\n        <title>Saturn Backup Cartridge - American Manual</title>\n        <id>urn:lrr:e69e43e1355267f7d32a4f9b7f2fe108d2401ebg</id>\n        <updated>2010-01-10T10:01:11Z</updated>\n        <published>2010-01-10T10:01:11Z</published>\n        <author>\n            <name></name>\n        </author>\n        <rights></rights>\n        <dcterms:language></dcterms:language>\n        <dcterms:publisher></dcterms:publisher>\n        <dcterms:issued></dcterms:issued>\n        \n        <category term=\"Archive\" />\n        \n        <summary>character:segata, female:very cool too</summary>\n\n        <link rel=\"alternate\" href=\"/api/opds/e69e43e1355267f7d32a4f9b7f2fe108d2401ebg\"\n            type=\"application/atom+xml;type=entry;profile=opds-catalog\" />\n\n        <link rel=\"http://opds-spec.org/image\" href=\"/api/archives/e69e43e1355267f7d32a4f9b7f2fe108d2401ebg/thumbnail\" type=\"image/jpeg\" />\n        <link rel=\"http://opds-spec.org/image/thumbnail\" href=\"/api/archives/e69e43e1355267f7d32a4f9b7f2fe108d2401ebg/thumbnail\"\n            type=\"image/jpeg\" />\n        <link rel=\"http://opds-spec.org/acquisition\" href=\"/api/archives/e69e43e1355267f7d32a4f9b7f2fe108d2401ebg/download\" title=\"Download/Read\"\n            type=\"application/x-cbz\" />\n        <link rel=\"http://vaemendis.net/opds-pse/stream\" type=\"image/jpeg\"\n            href=\"/api/opds/e69e43e1355267f7d32a4f9b7f2fe108d2401ebg/pse?page={pageNumber}\" pse:count=\"200\"  pse:lastRead=\"34\" />\n        <link type=\"text/html\" rel=\"alternate\" title=\"Open in LANraragi\" href=\"/reader?id=e69e43e1355267f7d32a4f9b7f2fe108d2401ebg\" />\n    </entry>\n    \n    <entry>\n        <title>Saturn Backup Cartridge - Japanese Manual</title>\n        <id>urn:lrr:e69e43e1355267f7d32a4f9b7f2fe108d2401ebf</id>\n        <updated>2010-01-10T10:01:11Z</updated>\n        <published>2010-01-10T10:01:11Z</published>\n        <author>\n            <name></name>\n        </author>\n        <rights></rights>\n        <dcterms:language></dcterms:language>\n        <dcterms:publisher></dcterms:publisher>\n        <dcterms:issued></dcterms:issued>\n        \n        <category term=\"Archive\" />\n        \n        <summary>character:segata sanshiro, male:very cool</summary>\n\n        <link rel=\"alternate\" href=\"/api/opds/e69e43e1355267f7d32a4f9b7f2fe108d2401ebf\"\n            type=\"application/atom+xml;type=entry;profile=opds-catalog\" />\n\n        <link rel=\"http://opds-spec.org/image\" href=\"/api/archives/e69e43e1355267f7d32a4f9b7f2fe108d2401ebf/thumbnail\" type=\"image/jpeg\" />\n        <link rel=\"http://opds-spec.org/image/thumbnail\" href=\"/api/archives/e69e43e1355267f7d32a4f9b7f2fe108d2401ebf/thumbnail\"\n            type=\"image/jpeg\" />\n        <link rel=\"http://opds-spec.org/acquisition\" href=\"/api/archives/e69e43e1355267f7d32a4f9b7f2fe108d2401ebf/download\" title=\"Download/Read\"\n            type=\"application/x-cbz\" />\n        <link rel=\"http://vaemendis.net/opds-pse/stream\" type=\"image/jpeg\"\n            href=\"/api/opds/e69e43e1355267f7d32a4f9b7f2fe108d2401ebf/pse?page={pageNumber}\" pse:count=\"2\"  pse:lastRead=\"10\" />\n        <link type=\"text/html\" rel=\"alternate\" title=\"Open in LANraragi\" href=\"/reader?id=e69e43e1355267f7d32a4f9b7f2fe108d2401ebf\" />\n    </entry>\n    \n\n</feed>\n"
                  }
                },
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "OPDS catalog XML"
          }
        },
        "summary": "Get the OPDS Catalog",
        "tags": [
          "opds"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/opds.rs:18"
      }
    },
    "/api/opds/{id}": {
      "get": {
        "description": "Returns a specific OPDS item as XML. This will show only one <entry\\> for the given ID in the result, instead of all the archives.",
        "operationId": "getOpdsItem",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/xml": {
                "examples": {
                  "xml": {
                    "summary": "A sample XML response",
                    "value": "<?xml version=\"1.0\" encoding=\"UTF-8\"?> <entry xmlns=\"http://www.w3.org/2005/Atom\" \n    xmlns:thr=\"http://purl.org/syndication/thread/1.0\"\n    xmlns:dcterms=\"http://purl.org/dc/terms/\" \n    xmlns:opds=\"http://opds-spec.org/2010/catalog\"\n    xmlns:pse=\"http://vaemendis.net/opds-pse/ns\" \n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xmlns:schema=\"http://schema.org/\">\n\n    <link rel=\"start\" href=\"/api/opds\" type=\"application/atom+xml;profile=opds-catalog;kind=navigation\" />\n    <link rel=\"self\" href=\"/api/opds/0a053b3a3960e85c331da2905513a18fa1375d99\" type=\"application/atom+xml;type=entry;profile=opds-catalog\" />\n\n    <title>Aya Sugimoto - [1989.05.15] 杉本彩写真集 Seductive</title>\n    <id>urn:lrr:0a053b3a3960e85c331da2905513a18fa1375d99</id>\n    <updated>2024-07-02T20:40:58Z</updated>\n    <published>2024-07-02T20:40:58Z</published>\n    <author>\n        <name></name>\n    </author>\n    <rights></rights>\n    <dcterms:language></dcterms:language>\n    <dcterms:publisher></dcterms:publisher>\n    <dcterms:issued></dcterms:issued>\n    \n    <category term=\"Archive\" />\n    \n    <summary>date_added:1719952858</summary>\n\n    <link rel=\"http://opds-spec.org/image\" href=\"/api/archives/0a053b3a3960e85c331da2905513a18fa1375d99/thumbnail\" type=\"image/jpeg\" />\n    <link rel=\"http://opds-spec.org/image/thumbnail\" href=\"/api/archives/0a053b3a3960e85c331da2905513a18fa1375d99/thumbnail\" type=\"image/jpeg\" />\n    <link rel=\"http://opds-spec.org/acquisition\" href=\"/api/archives/0a053b3a3960e85c331da2905513a18fa1375d99/download\" title=\"Download/Read\"\n        type=\"application/x-cbr\" />\n    <link rel=\"http://vaemendis.net/opds-pse/stream\" type=\"image/jpeg\"\n        href=\"/api/opds/0a053b3a3960e85c331da2905513a18fa1375d99/pse?page=%7BpageNumber%7D\" pse:count=\"33\" \n        \n        />\n    <link type=\"text/html\" rel=\"alternate\" title=\"Open in LANraragi\" href=\"/reader?id=0a053b3a3960e85c331da2905513a18fa1375d99\" />\n\n</entry>\n"
                  }
                },
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "OPDS entry XML"
          }
        },
        "summary": "Get a specific archive through OPDS",
        "tags": [
          "opds"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/opds.rs:19"
      }
    },
    "/api/opds/{id}/pse": {
      "get": {
        "description": "Returns a specific image page for OPDS-PSE as XML.",
        "operationId": "getOpdsPage",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/octet-stream": {
                "schema": {
                  "format": "binary",
                  "type": "string"
                }
              }
            },
            "description": "Image file for the requested page"
          }
        },
        "summary": "OPDS-PSE",
        "tags": [
          "opds"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/opds.rs:20"
      }
    },
    "/api/plugin-wizard/analyze-login": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `analyze_login` in crates/lanrurugi-api/src/plugin_wizard/analyze_login.rs:53.",
        "operationId": "analyzeLogin",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/plugin_wizard/mod.rs:22."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "summary": "analyze_login",
        "tags": [
          "plugin_wizard"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/plugin_wizard/mod.rs:22",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/plugin-wizard/generate/start": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/plugin_wizard/generate.rs:169), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`POST /plugin-wizard/generate/start` — see this module's own top-level docs for why generation\nis split into this plus `generate_stream` rather than one `POST` returning JSON. Accepts the\nrequest body as a raw [`serde_json::Value`] (not a typed `GenerateRequest`) specifically so\nthis endpoint never has to duplicate `GenerateRequest`'s own field/default handling — the real\ntyped parse happens once, at `generate_stream` time, against whatever was stashed here\nunmodified.",
        "operationId": "generateStart",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/plugin_wizard/mod.rs:26."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "summary": "`POST /plugin-wizard/generate/start` — see this module's own top-level docs for why generation",
        "tags": [
          "plugin_wizard"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/plugin_wizard/mod.rs:26",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/plugin-wizard/generate/stream/{id}": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/plugin_wizard/generate.rs:187), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`GET /plugin-wizard/generate/stream/{id}` — the real `EventSource`-compatible streaming\nendpoint; see this module's own top-level docs for the full event sequence. `id` is single-use\n(removed from `pending_generate_requests` the moment this is called), so replaying the same id\n(a stale bookmark, a double-click race) always yields `generation_not_found` rather than\nsilently re-running an old request.",
        "operationId": "generateStream",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/plugin_wizard/mod.rs:30."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "summary": "`GET /plugin-wizard/generate/stream/{id}` — the real `EventSource`-compatible streaming",
        "tags": [
          "plugin_wizard"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/plugin_wizard/mod.rs:30",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/plugin-wizard/lookup": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `lookup` in crates/lanrurugi-api/src/plugin_wizard/mod.rs:21.",
        "operationId": "lookup",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/plugin_wizard/mod.rs:21."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "summary": "lookup",
        "tags": [
          "plugin_wizard"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/plugin_wizard/mod.rs:21",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/plugin-wizard/save": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `save` in crates/lanrurugi-api/src/plugin_wizard/save.rs:71.",
        "operationId": "save",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/plugin_wizard/mod.rs:35."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "summary": "save",
        "tags": [
          "plugin_wizard"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/plugin_wizard/mod.rs:35",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/plugin-wizard/trial-run": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `trial_run` in crates/lanrurugi-api/src/plugin_wizard/trial_run.rs:114.",
        "operationId": "trialRun",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/plugin_wizard/mod.rs:34."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "summary": "trial_run",
        "tags": [
          "plugin_wizard"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/plugin_wizard/mod.rs:34",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/plugins/export": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/plugins.rs:1038), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`GET /plugins/export?namespace=...` (`specs/006-ai-plugin-wizard` FR-028) — downloads an\ninstalled plugin (wizard-created or not) as a `.zip` containing its own `.ts` file, named\nafter the namespace's own last path component (e.g. `custom/metadata/foo` → `foo.zip`\ncontaining `foo.ts`) rather than the full namespace, since the namespace's `/`-separated\ncategory prefix isn't meaningful once extracted standalone.",
        "operationId": "exportPlugin",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/plugins.rs:435."
          }
        },
        "summary": "`GET /plugins/export?namespace=...` (`specs/006-ai-plugin-wizard` FR-028) — downloads an",
        "tags": [
          "plugins"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/plugins.rs:435",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/plugins/export-batch": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/plugins.rs:1109), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`POST /plugins/export-batch` — like [`export_plugin`] but for several namespaces at once,\npacked into a single `.zip`. Each entry is stored under its full namespace path (`custom/\ndownload/foo.ts`, not just `foo.ts`) since two different namespaces can share the same file\nstem (`custom/download/foo` vs `custom/metadata/foo`) — collapsing both to `foo.ts` would\nsilently drop one on zip write.",
        "operationId": "exportPluginsBatch",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/plugins.rs:436."
          }
        },
        "summary": "`POST /plugins/export-batch` — like [`export_plugin`] but for several namespaces at once,",
        "tags": [
          "plugins"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/plugins.rs:436",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/plugins/options": {
      "delete": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `delete_plugin_options` in crates/lanrurugi-api/src/plugins.rs:931.",
        "operationId": "deletePluginOptions",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/plugins.rs:437."
          }
        },
        "summary": "delete_plugin_options",
        "tags": [
          "plugins"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/plugins.rs:437",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      },
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `get_plugin_options` in crates/lanrurugi-api/src/plugins.rs:785.",
        "operationId": "getPluginOptions",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/plugins.rs:437."
          }
        },
        "summary": "get_plugin_options",
        "tags": [
          "plugins"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/plugins.rs:437",
        "x-lanrurugi-undocumented": true
      },
      "put": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `put_plugin_options` in crates/lanrurugi-api/src/plugins.rs:854.",
        "operationId": "putPluginOptions",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/plugins.rs:437."
          }
        },
        "summary": "put_plugin_options",
        "tags": [
          "plugins"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/plugins.rs:437",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/plugins/priority": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/plugins.rs:567), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`PUT /plugins/priority` — persists a drag-and-drop reorder of one plugin `type` group's display\norder (additive, no legacy equivalent: legacy's `plugins.html.tt2` has no such affordance at\nall). Rewrites every listed namespace's `priority` field in its own `LRR_PLUGIN_<NS>` hash\n(same storage as `customargs`/`enabled`) to its position in `order`, so a subsequent\n`GET /plugins/{type}` sorts accordingly. A namespace whose actual type doesn't match `body.type`\nis skipped (not written, not erroring the whole request) — see `PutPluginPriorityBody::kind`'s\nown docs for why this check exists at all.",
        "operationId": "putPluginPriority",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/plugins.rs:432."
          }
        },
        "summary": "`PUT /plugins/priority` — persists a drag-and-drop reorder of one plugin `type` group's display",
        "tags": [
          "plugins"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/plugins.rs:432",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/plugins/queue": {
      "post": {
        "description": "Uses a Plugin and returns a Minion Job ID matching the Plugin run. This endpoint is useful if you want to run longer-lived plugins which might timeout if ran with the standard endpoint.\n",
        "operationId": "usePluginAsync",
        "parameters": [
          {
            "description": "ID of the archive to use the Plugin on. This is only mandatory for metadata plugins.",
            "in": "query",
            "name": "id",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ArchiveId"
            }
          },
          {
            "description": "Priority of the Minion job. The higher the number, the more important the job is.",
            "in": "query",
            "name": "priority",
            "required": false,
            "schema": {
              "default": 0,
              "type": "integer"
            }
          },
          {
            "description": "Namespace of the plugin to use.",
            "in": "query",
            "name": "plugin",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Optional One-Shot argument to use when executing this Plugin.",
            "in": "query",
            "name": "arg",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "job": 86,
                    "operation": "queue_plugin_exec",
                    "success": 1
                  },
                  "properties": {
                    "job": {
                      "description": "ID of the created Minion job",
                      "type": "integer"
                    },
                    "operation": {
                      "enum": [
                        "queue_plugin_exec"
                      ],
                      "type": "string"
                    },
                    "success": {
                      "enum": [
                        0,
                        1
                      ],
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Enqueued job"
          }
        },
        "summary": "Use a Plugin Asynchronously",
        "tags": [
          "plugins"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/plugins.rs:421",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/plugins/settings": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/plugins.rs:461), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`GET /plugins/settings?namespace=...` — the plugin's currently persisted `customargs` (padded/\ntruncated to exactly its own declared `parameters.len()` — a stale saved array longer/shorter\nthan the plugin's current declaration, e.g. after the plugin was upgraded to add/remove a\nparameter, is silently reconciled to the current shape rather than surfaced as a mismatch,\nmatching legacy's own default-value-fill-then-overwrite behavior in `get_plugin_parameters`)\nand `enabled` (legacy's \"Run Automatically\" toggle — `is_plugin_enabled`, `false` when never\nset, same as legacy's own `hexists`-gated default).",
        "operationId": "getPluginSettings",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/plugins.rs:422."
          }
        },
        "summary": "`GET /plugins/settings?namespace=...` — the plugin's currently persisted `customargs` (padded/",
        "tags": [
          "plugins"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/plugins.rs:422",
        "x-lanrurugi-undocumented": true
      },
      "put": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `put_plugin_settings` in crates/lanrurugi-api/src/plugins.rs:497.",
        "operationId": "putPluginSettings",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/plugins.rs:422."
          }
        },
        "summary": "put_plugin_settings",
        "tags": [
          "plugins"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/plugins.rs:422",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/plugins/upload": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `upload_plugin` in crates/lanrurugi-api/src/plugins.rs:3920.",
        "operationId": "uploadPlugin",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/plugins.rs:434."
          }
        },
        "summary": "upload_plugin",
        "tags": [
          "plugins"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/plugins.rs:434",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/plugins/use": {
      "post": {
        "description": "Uses a Plugin and returns the result.  If using a metadata plugin, the matching archive will not be modified in the database. See more info on Plugins in the matching section of the Docs.\n",
        "operationId": "usePluginSync",
        "parameters": [
          {
            "description": "ID of the archive to use the Plugin on. This is only mandatory for metadata plugins.",
            "in": "query",
            "name": "id",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/ArchiveId"
            }
          },
          {
            "description": "Namespace of the plugin to use.",
            "in": "query",
            "name": "plugin",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Optional One-Shot argument to use when executing this Plugin.",
            "in": "query",
            "name": "arg",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "data": {
                      "new_tags": "pages:45"
                    },
                    "operation": "use_plugin",
                    "success": 1,
                    "type": "metadata"
                  },
                  "properties": {
                    "data": {
                      "description": "Arbitrary data returned by the Plugin.",
                      "type": "object"
                    },
                    "error": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "operation": {
                      "enum": [
                        "use_plugin"
                      ],
                      "type": "string"
                    },
                    "success": {
                      "description": "Whether the Plugin run succeeded or not.",
                      "enum": [
                        0,
                        1
                      ],
                      "type": "integer"
                    },
                    "type": {
                      "description": "Plugin type. Omitted when the plugin is not found.",
                      "enum": [
                        "download",
                        "login",
                        "metadata",
                        "script"
                      ],
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Plugin result"
          }
        },
        "summary": "Use a Plugin",
        "tags": [
          "plugins"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/plugins.rs:420",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/plugins/{type}": {
      "get": {
        "description": "List all plugins of the given type.",
        "operationId": "listPlugins",
        "parameters": [
          {
            "description": "Type of plugins you want to list. You can use \"all\" to get all types at once.",
            "in": "path",
            "name": "type",
            "required": true,
            "schema": {
              "enum": [
                "download",
                "login",
                "metadata",
                "script",
                "all"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/PluginInfo"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Array of plugins"
          }
        },
        "summary": "List available plugins",
        "tags": [
          "plugins"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/plugins.rs:419"
      }
    },
    "/api/preferences": {
      "get": {
        "description": "The caller's own library-view preferences, falling back to the defaults for any value that has\nnever been set. An unauthenticated caller gets the defaults — not another user's settings.\n",
        "operationId": "getPreferences",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "library_sortby": {
                      "description": "The column the library view is sorted by.",
                      "type": "string"
                    },
                    "library_sortdir": {
                      "description": "Sort direction.",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "The effective preferences."
          }
        },
        "summary": "Read the caller's preferences",
        "tags": [
          "preferences"
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/preferences.rs:30"
      },
      "put": {
        "description": "Stores the caller's library-view preferences. Fields omitted from the request are left as they\nare. Requires an authenticated caller; an unauthenticated one is refused rather than silently\nwriting to a shared bucket.\n",
        "operationId": "putPreferences",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "library_sortby": {
                    "type": "string"
                  },
                  "library_sortdir": {
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "library_sortby": {
                      "type": "string"
                    },
                    "library_sortdir": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Stored; the body echoes the resulting values."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "No authenticated caller to scope the preferences to."
          }
        },
        "summary": "Update the caller's preferences",
        "tags": [
          "preferences"
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/preferences.rs:30",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/reader/recommendations/{id}": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `get_recommendations` in crates/lanrurugi-api/src/recommend.rs:438.",
        "operationId": "getRecommendations",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/recommend.rs:430."
          }
        },
        "summary": "get_recommendations",
        "tags": [
          "recommend"
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/recommend.rs:430",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/regen_thumbs": {
      "post": {
        "description": "Queue a Minion job to regenerate missing/all thumbnails on the server.",
        "operationId": "regenThumbnails",
        "parameters": [
          {
            "description": "Whether to generate all thumbnails, or only the missing ones.",
            "in": "query",
            "name": "force",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "job": 3,
                    "operation": "regen_thumbnails",
                    "success": 1
                  },
                  "properties": {
                    "job": {
                      "type": "integer"
                    },
                    "operation": {
                      "enum": [
                        "regen_thumbnails"
                      ],
                      "type": "string"
                    },
                    "success": {
                      "enum": [
                        0,
                        1
                      ],
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Enqueued job"
          }
        },
        "summary": "Regenerate Thumbnails",
        "tags": [
          "misc"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/archives.rs:253",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/reservations": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `list_reservations` in crates/lanrurugi-api/src/subscriptions/api.rs:1003.",
        "operationId": "listReservations",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/subscriptions/api.rs:54."
          }
        },
        "summary": "list_reservations",
        "tags": [
          "subscriptions"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/subscriptions/api.rs:54",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/reservations/{id}/discard": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `discard_reservation` in crates/lanrurugi-api/src/subscriptions/api.rs:1023.",
        "operationId": "discardReservation",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/subscriptions/api.rs:55."
          }
        },
        "summary": "discard_reservation",
        "tags": [
          "subscriptions"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/subscriptions/api.rs:55",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/search": {
      "get": {
        "description": "Search for Archives. You can use the IDs of this JSON with the other endpoints.\n\n<!-- lanrurugi:access-notes -->\n\n**Access differs by role**\n\n- A guest visitor (an unauthenticated visitor while guest access is enabled) sees only archives belonging to categories marked guest-visible. Everyone else sees the whole library.",
        "operationId": "searchArchives",
        "parameters": [
          {
            "description": "ID of the category you want to restrict this search to.",
            "in": "query",
            "name": "category",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/CategoryId"
            }
          },
          {
            "description": "Search query. You can use the following special characters in it:\n\n**Quotation Marks (\"...\")**\nExact string search. Allows a search term to include spaces. Everything placed inside a pair of quotation marks is treated as a singular term. Wildcard characters are still interpreted as wildcards.\n\n**Question Mark (?), Underscore (_)**\nWildcard. Can match any single character.\n\n**Asterisk (*), Percentage Sign (%)**\nWildcard. Can match any sequence of characters (including none).\n\n**Subtraction Sign (-)**\nExclusion. When placed before a term, prevents search results from including that term.\n\n**Dollar Sign ($)**\nAdd at the end of a tag to perform an exact tag search rather than displaying all elements that start with the term. Only matches tags regardless of search parameters and can be used as an exclusion to ignore misc tags in the search query.",
            "in": "query",
            "name": "filter",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "From which archive in the total result count this enumeration should start. The total number of archives displayed depends on the server-side page size preference.  \n\nFrom 0.8.2 onwards, you can use \"-1\" here to get the full, unpaged data.",
            "in": "query",
            "name": "start",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "description": "Namespace by which you want to sort the results. There are specific sort keys you can use:\n- *title* if you want to sort by title\n- *lastread* if you want to sort by last read time. (If **Server-side Progress Tracking** is enabled)  \n\n(Default value is title. If you sort by lastread, IDs that have never been read will be removed from the search.)",
            "in": "query",
            "name": "sortby",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Order of the sort, either `asc` or `desc`.",
            "in": "query",
            "name": "order",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Limit search to new archives only.",
            "in": "query",
            "name": "newonly",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "description": "Limit search to untagged archives only.",
            "in": "query",
            "name": "untaggedonly",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "description": "Hide archives where reading progress has reached the end.",
            "in": "query",
            "name": "hidecompleted",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "description": "Enable or disable Tankoubon grouping. Defaults to true.\nWhen enabled, Tankoubons will show in search results, replacing all the archive IDs they contain.",
            "in": "query",
            "name": "groupby_tanks",
            "required": false,
            "schema": {
              "default": true,
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "data": [
                      {
                        "arcid": "28697b96f0ac5858be2614ed10ca47742c9522fd",
                        "extension": "rar",
                        "filename": "Fate GO MEMO",
                        "isnew": false,
                        "lastreadtime": 1337038281,
                        "pagecount": 34,
                        "progress": 3,
                        "size": 0,
                        "summary": null,
                        "tags": "parody:fate grand order,  group:wadamemo,  artist:wada rco,  artbook,  full color",
                        "title": "Fate GO MEMO"
                      },
                      {
                        "arcid": "28697b96f0ac5858be2614ed10ca47742c9522fd",
                        "extension": "rar",
                        "filename": "Gucc",
                        "isnew": false,
                        "lastreadtime": 1337038281,
                        "pagecount": 34,
                        "progress": 3,
                        "size": 0,
                        "summary": "cool summary",
                        "tags": "parody:jojo's bizarre adventure",
                        "title": "Rohan Kishibe goes to Gucci"
                      }
                    ],
                    "recordsFiltered": 2,
                    "recordsTotal": 2
                  },
                  "properties": {
                    "data": {
                      "description": "Archive metadata objects for each search result",
                      "items": {
                        "$ref": "#/components/schemas/ArchiveMetadataJson"
                      },
                      "type": "array"
                    },
                    "recordsFiltered": {
                      "description": "Amount of archives in the search result",
                      "type": "integer"
                    },
                    "recordsTotal": {
                      "description": "Total number of archives available. This result will change depending on the `groupby_tanks` parameter.",
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Search results"
          },
          "204": {
            "description": "Search engine is not initialized yet. Please wait a few seconds."
          }
        },
        "summary": "Search Archives",
        "tags": [
          "search"
        ],
        "x-lanrurugi-access-notes": [
          "A guest visitor (an unauthenticated visitor while guest access is enabled) sees only archives belonging to categories marked guest-visible. Everyone else sees the whole library."
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/search.rs:194"
      }
    },
    "/api/search/archives": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/search.rs:435), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nAdditive raw-archive search endpoint (`GET /search/archives`), sharing `/search`'s query\nsyntax, filtering, sorting, and pagination but never folding member archives behind a\nTankoubon. It is intentionally a separate endpoint rather than a new default on `/search`:\nexisting grouped-search callers keep their exact behavior, while duplicate/library-membership\nchecks can ask for the underlying archive records instead of a `TANK_` aggregate.",
        "operationId": "searchArchivesUngrouped",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/search.rs:200."
          }
        },
        "summary": "Additive raw-archive search endpoint (`GET /search/archives`), sharing `/search`'s query",
        "tags": [
          "search"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/search.rs:200",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/search/cache": {
      "delete": {
        "description": "Discard the cache containing previous user searches.",
        "operationId": "discardSearchCache",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Success response"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "No archive ID response"
          }
        },
        "summary": "Discard Search Cache",
        "tags": [
          "search"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/search.rs:204",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/search/ids": {
      "get": {
        "description": "Search for Archives like `/api/search`, but returns only the ordered list of matching Archive IDs without the accompanying metadata.\n\n<!-- lanrurugi:access-notes -->\n\n**Access differs by role**\n\n- Scoped the same way as `/api/search`: a guest visitor receives ids only for guest-visible archives.",
        "operationId": "searchArchiveIds",
        "parameters": [
          {
            "description": "ID of the category you want to restrict this search to.",
            "in": "query",
            "name": "category",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/CategoryId"
            }
          },
          {
            "description": "Search query. This follows the same rules as the queries in `/api/search`.",
            "in": "query",
            "name": "filter",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "From which archive in the total result count this enumeration should start. The total number of archives displayed depends on the server-side page size preference.\n\nFrom 0.8.2 onwards, you can use \"-1\" here to get the full, unpaged list of IDs.",
            "in": "query",
            "name": "start",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "description": "Namespace by which you want to sort the results. There are specific sort keys you can use:\n- *title* if you want to sort by title\n- *lastread* if you want to sort by last read time. (If **Server-side Progress Tracking** is enabled)\n\n(Default value is title. If you sort by lastread, IDs that have never been read will be removed from the search.)",
            "in": "query",
            "name": "sortby",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Order of the sort, either `asc` or `desc`.",
            "in": "query",
            "name": "order",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Limit search to new archives only.",
            "in": "query",
            "name": "newonly",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "description": "Limit search to untagged archives only.",
            "in": "query",
            "name": "untaggedonly",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "description": "Hide archives where reading progress has reached the end.",
            "in": "query",
            "name": "hidecompleted",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "description": "Enable or disable Tankoubon grouping. Defaults to true.\nWhen enabled, Tankoubons will show in search results, replacing all the archive IDs they contain.",
            "in": "query",
            "name": "groupby_tanks",
            "required": false,
            "schema": {
              "default": true,
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "data": [
                      "28697b96f0ac5858be2614ed10ca47742c9522fd",
                      "5cf64ec2f3c2c627fb8a64df198af2a8527c4fdb"
                    ],
                    "recordsFiltered": 2,
                    "recordsTotal": 2
                  },
                  "properties": {
                    "data": {
                      "description": "Archive IDs for each search result",
                      "items": {
                        "$ref": "#/components/schemas/ArchiveId"
                      },
                      "type": "array"
                    },
                    "recordsFiltered": {
                      "description": "Amount of archives in the search result",
                      "type": "integer"
                    },
                    "recordsTotal": {
                      "description": "Total number of archives available. This result will change depending on the `groupby_tanks` parameter.",
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Search results"
          },
          "204": {
            "description": "Search engine is not initialized yet. Please wait a few seconds."
          }
        },
        "summary": "Search Archive IDs",
        "tags": [
          "search"
        ],
        "x-lanrurugi-access-notes": [
          "Scoped the same way as `/api/search`: a guest visitor receives ids only for guest-visible archives."
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/search.rs:201"
      }
    },
    "/api/search/random": {
      "get": {
        "description": "Get randomly selected Archives from the given filter and/or category.\n\n<!-- lanrurugi:access-notes -->\n\n**Access differs by role**\n\n- Scoped the same way as `/api/search`: a guest visitor draws only from guest-visible archives.",
        "operationId": "searchRandomArchives",
        "parameters": [
          {
            "description": "ID of the category you want to restrict this search to.",
            "in": "query",
            "name": "category",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/CategoryId"
            }
          },
          {
            "description": "Search query. This follows the same rules as the queries in `/api/search`.",
            "in": "query",
            "name": "filter",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "How many archives you want to pull randomly. Defaults to 5.  \n\nIf the search doesn't return enough data to match your count, you will get the full search shuffled randomly.",
            "in": "query",
            "name": "count",
            "required": false,
            "schema": {
              "default": 5,
              "type": "integer"
            }
          },
          {
            "description": "Limit search to new archives only.",
            "in": "query",
            "name": "newonly",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "description": "Limit search to untagged archives only.",
            "in": "query",
            "name": "untaggedonly",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "description": "Hide archives where reading progress has reached the end.",
            "in": "query",
            "name": "hidecompleted",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "description": "Enable or disable Tankoubon grouping. Defaults to true.\nWhen enabled, Tankoubons will show in search results, replacing all the archive IDs they contain.",
            "in": "query",
            "name": "groupby_tanks",
            "required": false,
            "schema": {
              "default": true,
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": [
                    {
                      "arcid": "28697b96f0ac5858be2614ed10ca47742c9522fd",
                      "extension": "rar",
                      "filename": "Gucc",
                      "isnew": false,
                      "lastreadtime": 1337038281,
                      "pagecount": 34,
                      "progress": 3,
                      "size": 0,
                      "summary": "cool summary",
                      "tags": "parody:jojo's bizarre adventure",
                      "title": "Rohan Kishibe goes to Gucci"
                    }
                  ],
                  "recordsTotal": 2
                },
                "schema": {
                  "properties": {
                    "data": {
                      "description": "Archive metadata objects for each search result",
                      "items": {
                        "$ref": "#/components/schemas/ArchiveMetadataJson"
                      },
                      "type": "array"
                    },
                    "recordsTotal": {
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Search results"
          }
        },
        "summary": "Search random Archives",
        "tags": [
          "search"
        ],
        "x-lanrurugi-access-notes": [
          "Scoped the same way as `/api/search`: a guest visitor draws only from guest-visible archives."
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/search.rs:203"
      }
    },
    "/api/search/suggest": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/search.rs:603), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nAdditive, lightweight endpoint backing the Library search box's title autocomplete. Unlike\n`/search`, it returns only `arcid` + `title` for the first few matches — no tags, progress, or\nsize — because the frontend calls it on every (debounced) keystroke, where a full `/search`\npage of 100 archive records would be a few hundred KB of JSON that a suggestion list\nimmediately discards. Same query syntax, same filters, and the same default `title` ordering as\n`/search` (which is exactly what a suggestion list wants), plus the same guest scoping:\n`restrict_to_archive_ids` is applied here too, so a `guest_visitor` can never be shown an\nout-of-scope title through this endpoint that `/search` would refuse to return.\n\n<!-- lanrurugi:access-notes -->\n\n**Access differs by role**\n\n- Scoped the same way as `/api/search`, so a guest visitor cannot see the title of an archive that `/api/search` would refuse to return.",
        "operationId": "searchSuggest",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/search.rs:202."
          }
        },
        "summary": "Additive, lightweight endpoint backing the Library search box's title autocomplete",
        "tags": [
          "search"
        ],
        "x-lanrurugi-access-notes": [
          "Scoped the same way as `/api/search`, so a guest visitor cannot see the title of an archive that `/api/search` would refuse to return."
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/search.rs:202",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/sessions": {
      "get": {
        "description": "Every active refresh-token family — that is, every device currently signed in — including the\none making this request, which is marked so a client does not offer to revoke itself by\naccident.\n\nRequires a session-cookie login. An API token of either role is rejected.\n",
        "operationId": "listSessions",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/Session"
                  },
                  "type": "array"
                }
              }
            },
            "description": "The active sessions."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "summary": "List signed-in devices",
        "tags": [
          "sessions"
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/sessions.rs:28"
      }
    },
    "/api/sessions/{family_id}": {
      "delete": {
        "description": "Ends one session by revoking its whole refresh-token family, so the device must sign in again.\nRequires a session-cookie login.\n",
        "operationId": "revokeSession",
        "parameters": [
          {
            "in": "path",
            "name": "family_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Revoked."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "No such session."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "summary": "Revoke a signed-in device",
        "tags": [
          "sessions"
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/sessions.rs:28"
      },
      "patch": {
        "description": "Sets a custom display name for one session, so a device list reads as \"living room tablet\"\nrather than as a user-agent string. Requires a session-cookie login.\n",
        "operationId": "renameSession",
        "parameters": [
          {
            "in": "path",
            "name": "family_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "name": {
                    "description": "The new display name.",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Renamed."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "No such session."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "summary": "Rename a signed-in device",
        "tags": [
          "sessions"
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/sessions.rs:28"
      }
    },
    "/api/settings": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `get_settings` in crates/lanrurugi-api/src/settings.rs:624.",
        "operationId": "getSettings",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/settings.rs:59."
          }
        },
        "summary": "get_settings",
        "tags": [
          "settings"
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/settings.rs:59",
        "x-lanrurugi-undocumented": true
      },
      "put": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `put_settings` in crates/lanrurugi-api/src/settings.rs:751.",
        "operationId": "putSettings",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/settings.rs:59."
          }
        },
        "summary": "put_settings",
        "tags": [
          "settings"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/settings.rs:59",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/settings/password": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/settings.rs:1017), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nSets a new admin password, hashed the same way legacy stores one (`{CRYPT}$2a$...` — see\n`lanrurugi_core::password`) so a legacy instance sharing this Redis keeps accepting it too. An\nempty string clears password protection's practical effect the same way legacy's own\nconfig-page \"leave blank to keep current password\" convention does *not* work here — this\nendpoint always sets whatever was submitted, so the frontend must not submit an empty field\nunless the user explicitly means to set an empty password.",
        "operationId": "changePassword",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/settings.rs:60."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "summary": "Sets a new admin password, hashed the same way legacy stores one (`{CRYPT}$2a$...` — see",
        "tags": [
          "settings"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/settings.rs:60",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/shinobu": {
      "get": {
        "description": "Get the current status of the FileWatcher.",
        "operationId": "shinobuStatus",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "is_alive": 1,
                    "operation": "shinobu_status",
                    "pid": 1608,
                    "success": 1
                  },
                  "properties": {
                    "is_alive": {
                      "enum": [
                        0,
                        1
                      ],
                      "type": "integer"
                    },
                    "operation": {
                      "enum": [
                        "shinobu_status"
                      ],
                      "type": "string"
                    },
                    "pid": {
                      "description": "Current PID of the Watcher process",
                      "type": "integer"
                    },
                    "success": {
                      "enum": [
                        0,
                        1
                      ],
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Status"
          }
        },
        "summary": "Get Shinobu status",
        "tags": [
          "shinobu"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/shinobu.rs:16"
      }
    },
    "/api/shinobu/rescan": {
      "post": {
        "description": "This deletes the internal map of scanned files on your system (the \"filemap\") and restarts Shinobu, effectively prompting a full rescan of your FS.",
        "operationId": "shinobuRescan",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "new_pid": 1727,
                    "operation": "shinobu_rescan",
                    "success": 1
                  },
                  "properties": {
                    "new_pid": {
                      "description": "PID of the new Watcher process.",
                      "type": "integer"
                    },
                    "operation": {
                      "enum": [
                        "shinobu_rescan"
                      ],
                      "type": "string"
                    },
                    "success": {
                      "enum": [
                        0,
                        1
                      ],
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Result"
          }
        },
        "summary": "Rescan filemap and restart Shinobu",
        "tags": [
          "shinobu"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/shinobu.rs:19",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/shinobu/restart": {
      "post": {
        "description": "Restart the Shinobu filewatcher",
        "operationId": "shinobuRestart",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "new_pid": 1727,
                    "operation": "shinobu_restart",
                    "success": 1
                  },
                  "properties": {
                    "new_pid": {
                      "description": "PID of the new Watcher process.",
                      "type": "integer"
                    },
                    "operation": {
                      "enum": [
                        "shinobu_restart"
                      ],
                      "type": "string"
                    },
                    "success": {
                      "enum": [
                        0,
                        1
                      ],
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Result"
          }
        },
        "summary": "Restart Shinobu",
        "tags": [
          "shinobu"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/shinobu.rs:18",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/shinobu/stop": {
      "post": {
        "description": "Stops the Filewatcher. Use `/api/shinobu/restart` to start it again.",
        "operationId": "shinobuStop",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Result"
          }
        },
        "summary": "Stop Shinobu",
        "tags": [
          "shinobu"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/shinobu.rs:17",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/stamps/{id}": {
      "delete": {
        "description": "Remove a stamp from an Archive.",
        "operationId": "deleteStamp",
        "parameters": [
          {
            "description": "ID of the stamp.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Success response"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Error response"
          },
          "423": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "The Stamp is currently locked for modification"
          }
        },
        "summary": "Delete Stamp",
        "tags": [
          "stamps"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/stamps.rs:39",
        "x-lanrurugi-token-role": "admin"
      },
      "get": {
        "description": "Get a stamp from an Archive.",
        "operationId": "getStamp",
        "parameters": [
          {
            "description": "ID of the stamp.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StampsData"
                }
              }
            },
            "description": "Success response"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Error response"
          },
          "423": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "The Stamp is currently locked for modification"
          }
        },
        "summary": "Get Stamp",
        "tags": [
          "stamps"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/stamps.rs:39"
      },
      "put": {
        "description": "Update a stamp from an Archive.",
        "operationId": "updateStamp",
        "parameters": [
          {
            "description": "ID of the stamp.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Text of the stamp.",
            "in": "query",
            "name": "content",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Position of the stamp in the page.",
            "in": "query",
            "name": "position",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Success response"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Error response"
          },
          "423": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "The Stamp is currently locked for modification"
          }
        },
        "summary": "Update Stamp",
        "tags": [
          "stamps"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/stamps.rs:39",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/subscriptions": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `list` in crates/lanrurugi-api/src/subscriptions/api.rs:168.",
        "operationId": "list",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/subscriptions/api.rs:27."
          }
        },
        "summary": "list",
        "tags": [
          "subscriptions"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/subscriptions/api.rs:27",
        "x-lanrurugi-undocumented": true
      },
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `create` in crates/lanrurugi-api/src/subscriptions/api.rs:245.",
        "operationId": "create",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/subscriptions/api.rs:27."
          }
        },
        "summary": "create",
        "tags": [
          "subscriptions"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/subscriptions/api.rs:27",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/subscriptions/blocked": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/subscriptions/api.rs:413), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nEvery work the user has explicitly blocked, newest first, across all subscriptions.\n\nIts own endpoint rather than a filter over the history: a block is an *answer* — the user said no\nto this work — and the history is a log of decisions that keeps being rewritten by later cycles\n(a work queued and then catalogued reads `already_held` in every record). Only the block list\nstays exactly as the user left it, which is what makes it worth showing on its own and undoing\nfrom there.",
        "operationId": "blockedWorks",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/subscriptions/api.rs:53."
          }
        },
        "summary": "Every work the user has explicitly blocked, newest first, across all subscriptions",
        "tags": [
          "subscriptions"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/subscriptions/api.rs:53",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/subscriptions/condition/ai": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `generate` in crates/lanrurugi-api/src/subscriptions/ai_condition.rs:302.",
        "operationId": "generate",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/subscriptions/api.rs:38."
          }
        },
        "summary": "generate",
        "tags": [
          "subscriptions"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/subscriptions/api.rs:38",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/subscriptions/condition/parse": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `parse` in crates/lanrurugi-api/src/subscriptions/ai_condition.rs:285.",
        "operationId": "parse",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/subscriptions/api.rs:39."
          }
        },
        "summary": "parse",
        "tags": [
          "subscriptions"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/subscriptions/api.rs:39",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/subscriptions/history": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/subscriptions/api.rs:446), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nEvery subscription's recent candidates, newest first, as one stream.\n\nPer-subscription history already exists, but it answers \"what did *this* rule do\" — the question\nactually asked is usually the other one: what has arrived lately, and why did something expected\nnot. That needs the subscriptions interleaved rather than read one at a time.",
        "operationId": "combinedHistory",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/subscriptions/api.rs:52."
          }
        },
        "summary": "Every subscription's recent candidates, newest first, as one stream",
        "tags": [
          "subscriptions"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/subscriptions/api.rs:52",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/subscriptions/pending": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/subscriptions/api.rs:855), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nMatched works waiting for the user's go-ahead. Only reachable because `auto_download` defaults\noff — without this, that default would leave candidates recorded but unanswerable.",
        "operationId": "listPending",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/subscriptions/api.rs:31."
          }
        },
        "summary": "Matched works waiting for the user's go-ahead",
        "tags": [
          "subscriptions"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/subscriptions/api.rs:31",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/subscriptions/pending/approve": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/subscriptions/api.rs:888), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nApproving is the moment unattended matching becomes attended spending — nothing before this point\nconsumes download credit.\n\nPartial success is reported rather than rolled back: with several items, one that fails to queue\nis no reason to un-approve the rest, and the caller is told exactly which ones did not make it.",
        "operationId": "approvePending",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/subscriptions/api.rs:32."
          }
        },
        "summary": "Approving is the moment unattended matching becomes attended spending — nothing before this point",
        "tags": [
          "subscriptions"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/subscriptions/api.rs:32",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/subscriptions/pending/dismiss": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/subscriptions/api.rs:976), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nDismissing marks each work seen, so later checks do not raise the same question again. This is\nthe user answering \"no\" — distinct from a reservation, which is the system reporting it *could*\nnot download something.",
        "operationId": "dismissPending",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/subscriptions/api.rs:33."
          }
        },
        "summary": "Dismissing marks each work seen, so later checks do not raise the same question again",
        "tags": [
          "subscriptions"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/subscriptions/api.rs:33",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/subscriptions/preview": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/subscriptions/api.rs:740), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nPreviews a subscription that does not exist yet.\n\nThe id-bearing route cannot serve this: there is no record to read. Yet a subscription being\ncreated is exactly when \"what would this actually catch\" matters most — afterwards the user has\nalready committed to the rule. The draft is evaluated against the source as it is now, and nothing\nis stored either way.",
        "operationId": "previewDraft",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/subscriptions/api.rs:36."
          }
        },
        "summary": "Previews a subscription that does not exist yet",
        "tags": [
          "subscriptions"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/subscriptions/api.rs:36",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/subscriptions/preview/judge": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/subscriptions/api.rs:680), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nRe-judges a listing already fetched, against possibly-different conditions.\n\nChanging a condition changes nothing about what the source would return — only `criteria` shapes\nthe request — so re-fetching would be wasted, slow, and one more hit on a source that rate-limits.\nThe judgment still happens here rather than in the browser, because a second implementation of the\ncondition tree would eventually disagree with the real one.",
        "operationId": "previewJudge",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/subscriptions/api.rs:37."
          }
        },
        "summary": "Re-judges a listing already fetched, against possibly-different conditions",
        "tags": [
          "subscriptions"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/subscriptions/api.rs:37",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/subscriptions/sources": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/subscriptions/api.rs:182), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nWhich sources can back a subscription, with their interval bounds — so the UI can offer only\nviable sources and show the bounds *before* the user submits, rather than teaching them the\nrules through a rejection.",
        "operationId": "listSources",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/subscriptions/api.rs:28."
          }
        },
        "summary": "Which sources can back a subscription, with their interval bounds — so the UI can offer only",
        "tags": [
          "subscriptions"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/subscriptions/api.rs:28",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/subscriptions/tracking": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/subscriptions/api.rs:551), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nWhich subscriptions have already handled a given work, by its source URL.\n\nAsked before deleting an archive, so the delete flow only offers \"stop this coming back\" for an\narchive a subscription actually brought in — offering it for a hand-uploaded file would be an\noption with no effect, which is worse than no option at all.",
        "operationId": "trackingFor",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/subscriptions/api.rs:51."
          }
        },
        "summary": "Which subscriptions have already handled a given work, by its source URL",
        "tags": [
          "subscriptions"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/subscriptions/api.rs:51",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/subscriptions/{id}": {
      "delete": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `remove` in crates/lanrurugi-api/src/subscriptions/api.rs:348.",
        "operationId": "remove",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/subscriptions/api.rs:40."
          }
        },
        "summary": "remove",
        "tags": [
          "subscriptions"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/subscriptions/api.rs:40",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      },
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `detail` in crates/lanrurugi-api/src/subscriptions/api.rs:288.",
        "operationId": "detail",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/subscriptions/api.rs:40."
          }
        },
        "summary": "detail",
        "tags": [
          "subscriptions"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/subscriptions/api.rs:40",
        "x-lanrurugi-undocumented": true
      },
      "put": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `update` in crates/lanrurugi-api/src/subscriptions/api.rs:307.",
        "operationId": "update",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/subscriptions/api.rs:40."
          }
        },
        "summary": "update",
        "tags": [
          "subscriptions"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/subscriptions/api.rs:40",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/subscriptions/{id}/block": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/subscriptions/api.rs:640), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nThe user's own \"no\": these works are never downloaded by this subscription again.\n\nDistinct from a rule rejection on purpose — a rule reads a value that can change, so a rejection is\nprovisional and decided afresh every cycle (FR-011i), while this is an answer, and re-deciding it\nwould ask again what the user already said. The preview offers it per row; `forget_seen` undoes it.",
        "operationId": "blockSources",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/subscriptions/api.rs:50."
          }
        },
        "summary": "The user's own \"no\": these works are never downloaded by this subscription again",
        "tags": [
          "subscriptions"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/subscriptions/api.rs:50",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/subscriptions/{id}/check": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/subscriptions/api.rs:842), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nRuns a real check now rather than waiting for the schedule (FR-024).\n\nSpawned rather than awaited: a check contacts the source and may page through it, which is longer\nthan a request should hold open. The result shows up in the subscription's own history.",
        "operationId": "checkNow",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/subscriptions/api.rs:48."
          }
        },
        "summary": "Runs a real check now rather than waiting for the schedule (FR-024)",
        "tags": [
          "subscriptions"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/subscriptions/api.rs:48",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/subscriptions/{id}/disable": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `disable` in crates/lanrurugi-api/src/subscriptions/api.rs:384.",
        "operationId": "disable",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/subscriptions/api.rs:45."
          }
        },
        "summary": "disable",
        "tags": [
          "subscriptions"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/subscriptions/api.rs:45",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/subscriptions/{id}/enable": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `enable` in crates/lanrurugi-api/src/subscriptions/api.rs:374.",
        "operationId": "enable",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/subscriptions/api.rs:44."
          }
        },
        "summary": "enable",
        "tags": [
          "subscriptions"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/subscriptions/api.rs:44",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/subscriptions/{id}/forget": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/subscriptions/api.rs:593), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nMakes a subscription reconsider works it has already handled.\n\nBeing seen is otherwise permanent, which is correct for the ordinary case — a work the user\ndeleted on purpose must not come back on the next check. But two situations need it undone: a\ndeletion made by mistake, and a rule that has since been widened (works rejected under the old\nrule were recorded as seen at the moment they were rejected, so nothing would reconsider them).\n\nWith `sources`, forgets exactly those. With `all: true`, clears the set — kept as a separate,\nexplicit request because on a long-running subscription it can match the entire back catalogue at\nonce, which is a different order of consequence.",
        "operationId": "forgetSeen",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/subscriptions/api.rs:49."
          }
        },
        "summary": "Makes a subscription reconsider works it has already handled",
        "tags": [
          "subscriptions"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/subscriptions/api.rs:49",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/subscriptions/{id}/preview": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/subscriptions/api.rs:799), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nWhat a check would do, without doing it.\n\nDeliberately works on a **disabled** subscription: \"what would this rule actually catch\" is asked\nprecisely while tuning one that is switched off. Nothing is queued, recorded as seen, or logged,\nso it is safe to run repeatedly — the only cost is one request to the source.\n\nAccepts an optional body carrying an unsaved subscription, so a rule can be previewed before it is\ncommitted. Without that, a user would have to save a rule to find out what it does.",
        "operationId": "preview",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/subscriptions/api.rs:47."
          }
        },
        "summary": "What a check would do, without doing it",
        "tags": [
          "subscriptions"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/subscriptions/api.rs:47",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/subscriptions/{id}/resume": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/subscriptions/api.rs:396), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nClears a system-applied pause. Distinct from `enable`, because a paused subscription was stopped\n*by the system* and owes the user an explanation — resuming is the user answering it.",
        "operationId": "resume",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/subscriptions/api.rs:46."
          }
        },
        "summary": "Clears a system-applied pause",
        "tags": [
          "subscriptions"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/subscriptions/api.rs:46",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/tankoubons": {
      "get": {
        "description": "Get list of Tankoubons paginated.",
        "operationId": "getTankoubonList",
        "parameters": [
          {
            "description": "Page of the list of Tankoubons.  \nThe amount of tanks per page depends on the server `archives_per_page` setting.",
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "filtered": {
                      "description": "Number of Tankoubons on the current page",
                      "type": "integer"
                    },
                    "result": {
                      "items": {
                        "$ref": "#/components/schemas/TankoubonMetadataJson"
                      },
                      "type": "array"
                    },
                    "total": {
                      "description": "Total count of Tankoubons on the server",
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Tankoubons"
          }
        },
        "summary": "Get all Tankoubons",
        "tags": [
          "tankoubons"
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/tankoubons.rs:80"
      },
      "put": {
        "description": "Create a new Tankoubon or update the name of an existing one.",
        "operationId": "createTankoubon",
        "requestBody": {
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "properties": {
                  "name": {
                    "description": "Name of the Tankoubon",
                    "type": "string"
                  },
                  "tankid": {
                    "description": "ID of an existing Tankoubon, if you want to change its name.",
                    "type": "string"
                  }
                },
                "required": [
                  "name"
                ],
                "type": "object"
              }
            },
            "multipart/form-data": {
              "schema": {
                "properties": {
                  "name": {
                    "description": "Name of the Tankoubon",
                    "type": "string"
                  },
                  "tankid": {
                    "description": "ID of an existing Tankoubon, if you want to change its name.",
                    "type": "string"
                  }
                },
                "required": [
                  "name"
                ],
                "type": "object"
              }
            }
          },
          "required": false
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "operation": {
                      "enum": [
                        "create_tankoubon"
                      ],
                      "type": "string"
                    },
                    "success": {
                      "enum": [
                        0,
                        1
                      ],
                      "type": "integer"
                    },
                    "tankoubon_id": {
                      "description": "ID of the created/modified Tankoubon",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Success response"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Error response"
          }
        },
        "summary": "Create a Tankoubon",
        "tags": [
          "tankoubons"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/tankoubons.rs:80",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/tankoubons/ai-group-suggestions": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `ai_group_suggestions` in crates/lanrurugi-api/src/tankoubon_grouping.rs:581.",
        "operationId": "aiGroupSuggestions",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/tankoubon_grouping.rs:90."
          }
        },
        "summary": "ai_group_suggestions",
        "tags": [
          "tankoubon_grouping"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/tankoubon_grouping.rs:90",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/tankoubons/ai-group-suggestions/ignore": {
      "delete": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/tankoubon_grouping.rs:903), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`DELETE /tankoubons/ai-group-suggestions/ignore` — re-enables a previously-ignored suggestion\n(the frontend's \"Show ignored combinations\" checklist's own \"Un-ignore\" button). Idempotent —\nun-ignoring a combination that isn't currently ignored is a no-op, not an error.",
        "operationId": "unignoreGroupSuggestion",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/tankoubon_grouping.rs:94."
          }
        },
        "summary": "`DELETE /tankoubons/ai-group-suggestions/ignore` — re-enables a previously-ignored suggestion",
        "tags": [
          "tankoubon_grouping"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/tankoubon_grouping.rs:94",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      },
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/tankoubon_grouping.rs:867), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`POST /tankoubons/ai-group-suggestions/ignore` — \"Don't suggest this again\" for one specific\nsuggestion. Idempotent (ignoring an already-ignored combination just overwrites its\n`ignored_at` timestamp, per `IgnoredGroupSuggestionsRepository::ignore`'s own contract).",
        "operationId": "ignoreGroupSuggestion",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/tankoubon_grouping.rs:94."
          }
        },
        "summary": "`POST /tankoubons/ai-group-suggestions/ignore` — \"Don't suggest this again\" for one specific",
        "tags": [
          "tankoubon_grouping"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/tankoubon_grouping.rs:94",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/tankoubons/ai-group-suggestions/ignored": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/tankoubon_grouping.rs:925), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`GET /tankoubons/ai-group-suggestions/ignored` — backs the \"Show ignored combinations\"\nchecklist itself (titles are resolved client-side against the already-loaded archive list, the\nsame `titleById` map `AiSmartTankoubonModal.tsx` already builds for the main suggestion cards —\nthis endpoint only needs to return the raw ignored entries, not hydrate them server-side).",
        "operationId": "listIgnoredGroupSuggestions",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/tankoubon_grouping.rs:98."
          }
        },
        "summary": "`GET /tankoubons/ai-group-suggestions/ignored` — backs the \"Show ignored combinations\"",
        "tags": [
          "tankoubon_grouping"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/tankoubon_grouping.rs:98",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/tankoubons/{id}": {
      "delete": {
        "description": "Remove a Tankoubon from the server. This doesn't delete underlying Archives.",
        "operationId": "deleteTankoubon",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Success response"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Error response"
          },
          "423": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "The Tankoubon is currently locked for modification"
          }
        },
        "summary": "Delete Tankoubon",
        "tags": [
          "tankoubons"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/tankoubons.rs:84",
        "x-lanrurugi-token-role": "admin"
      },
      "get": {
        "description": "Get the details of the specified tankoubon ID.",
        "operationId": "getTankoubon",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "archives": {
                      "description": "Full list of archive IDs contained in this Tankoubon.",
                      "items": {
                        "oneOf": [
                          {
                            "$ref": "#/components/schemas/ArchiveId"
                          },
                          {
                            "$ref": "#/components/schemas/TankoubonId"
                          }
                        ],
                        "type": "string"
                      },
                      "type": "array",
                      "uniqueItems": true
                    },
                    "id": {
                      "description": "ID of this Tankoubon.",
                      "type": "string"
                    },
                    "name": {
                      "description": "Name of this Tankoubon.",
                      "type": "string"
                    },
                    "progress": {
                      "description": "Reading progress (page number) for this Tankoubon.",
                      "type": "integer"
                    },
                    "summary": {
                      "type": "string"
                    },
                    "tags": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Tankoubon"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Error response"
          }
        },
        "summary": "Get a single Tankoubon",
        "tags": [
          "tankoubons"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/tankoubons.rs:84"
      },
      "put": {
        "description": "Modify the full metadata (name, summary, additional tags) or the contents of a Tankoubon.  \nIf you only need to change the name of Tank, consider just using PUT `/api/tankoubons`.  \n\nNote: If there is no need to update something in one of the `data` keys, do not send the key -- This otherwise can result in unwanted results.",
        "operationId": "updateTankoubon",
        "parameters": [
          {
            "description": "ID of the Tankoubon to update.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/TankoubonId"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "archives": {
                    "description": "Ordered array with the IDs of the archives.",
                    "items": {
                      "$ref": "#/components/schemas/ArchiveId"
                    },
                    "type": "array"
                  },
                  "metadata": {
                    "description": "Metadata parameters -- All are optional.",
                    "properties": {
                      "append": {
                        "description": "If true, tags are appended to the Tank's existing own tags instead of replacing them. Defaults to false.",
                        "type": "boolean"
                      },
                      "name": {
                        "description": "Name of the Tankoubon",
                        "type": "string"
                      },
                      "summary": {
                        "description": "Summary of the Tankoubon",
                        "type": "string"
                      },
                      "tags": {
                        "description": "Additional tags for the Tankoubon, in LRR comma-separated format. This will replace whatever additional tags the Tank already has, unless append=true.",
                        "type": "string"
                      }
                    },
                    "type": "object"
                  }
                },
                "type": "object"
              }
            }
          },
          "description": "Json with 2 optional keys \"archives\" and \"metadata\"",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Success response"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Error response"
          },
          "423": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "The Tankoubon is currently locked for modification"
          }
        },
        "summary": "Update Tankoubon metadata/contents",
        "tags": [
          "tankoubons"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/tankoubons.rs:84",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/tankoubons/{id}/ai/rename-chapter": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `ai_rename_chapter` in crates/lanrurugi-api/src/tankoubons.rs:1693.",
        "operationId": "aiRenameChapter",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/tankoubons.rs:107."
          }
        },
        "summary": "ai_rename_chapter",
        "tags": [
          "tankoubons"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/tankoubons.rs:107",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/tankoubons/{id}/ai/rename-suggestions": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/tankoubons.rs:1585), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nPOST /api/tankoubons/{id}/ai/rename-suggestions\n\nSends the tankoubon title, member archive names, and order to DeepSeek and returns\nAI-generated rename suggestions (tankoubon name + per-archive chapter names).\nThe LLM key must be configured; returns 400 if not.",
        "operationId": "aiRenameSuggestions",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/tankoubons.rs:103."
          }
        },
        "summary": "POST /api/tankoubons/{id}/ai/rename-suggestions",
        "tags": [
          "tankoubons"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/tankoubons.rs:103",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/tankoubons/{id}/bookmarks": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/bookmarks.rs:877), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`GET /tankoubons/{id}/bookmarks` — every bookmark across every member archive, translated into\nthe Tankoubon's own global page numbering. Unpaginated, ascending by global page — matches\n`list_bookmarks_for_archive`'s own contract (used the same way, by `BookmarkHoverGrid`, just\nfor a Tankoubon card instead of a plain archive card). A member archive that no longer exists\n(or has an empty `pagecount`, e.g. not yet scanned) contributes nothing, silently, rather than\nfailing the whole request — same tolerance `TankoubonFullResponse`'s own docs describe for\n`full_data` silently omitting a missing member.",
        "operationId": "listBookmarksForTankoubon",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/bookmarks.rs:64."
          }
        },
        "summary": "`GET /tankoubons/{id}/bookmarks` — every bookmark across every member archive, translated into",
        "tags": [
          "bookmarks"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/bookmarks.rs:64",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/tankoubons/{id}/bookmarks/{page}": {
      "delete": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/bookmarks.rs:832), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`DELETE /tankoubons/{id}/bookmarks/{page}` — same resolve-then-delegate shape as\n[`add_tank_bookmark`], onto [`remove_bookmark`].",
        "operationId": "removeTankBookmark",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/bookmarks.rs:68."
          }
        },
        "summary": "`DELETE /tankoubons/{id}/bookmarks/{page}` — same resolve-then-delegate shape as",
        "tags": [
          "bookmarks"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/bookmarks.rs:68",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      },
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/bookmarks.rs:817), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`POST /tankoubons/{id}/bookmarks/{page}` — resolves the Tankoubon-global `page` down to the\nreal member archive + local page (`tankoubons::resolve_tank_page`) and delegates to\n[`add_bookmark`]'s own logic; the write itself still lands on the real archive id, same as a\nstamp placed while reading a Tankoubon (`stamps.rs`'s own Tankoubon routes) — `bookmarks.rs`'s\nunderlying `BookmarksRepository` has no `TANK_` concept of its own and never needs one.",
        "operationId": "addTankBookmark",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/bookmarks.rs:68."
          }
        },
        "summary": "`POST /tankoubons/{id}/bookmarks/{page}` — resolves the Tankoubon-global `page` down to the",
        "tags": [
          "bookmarks"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/bookmarks.rs:68",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/tankoubons/{id}/bookmarks/{page}/name": {
      "put": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/bookmarks.rs:665), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`PUT /tankoubons/{id}/bookmarks/{page}/name` — same resolve-then-delegate shape as\n`add_tank_bookmark`/`remove_tank_bookmark`, onto [`rename_bookmark`].",
        "operationId": "renameTankBookmark",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/bookmarks.rs:72."
          }
        },
        "summary": "`PUT /tankoubons/{id}/bookmarks/{page}/name` — same resolve-then-delegate shape as",
        "tags": [
          "bookmarks"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/bookmarks.rs:72",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/tankoubons/{id}/full": {
      "get": {
        "description": "Get the details of the specified tankoubon ID with paginated archive metadata.\nThe amount of archives per page depends on the server `archives_per_page` setting.",
        "operationId": "getTankoubonFull",
        "parameters": [
          {
            "description": "Page of the Archives list. Defaults to -1, which returns all archives.",
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "default": -1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "filtered": {
                      "description": "The amount of archives in the current page.",
                      "type": "integer"
                    },
                    "result": {
                      "properties": {
                        "archives": {
                          "description": "Paginated list of archive IDs contained in this Tankoubon.",
                          "items": {
                            "oneOf": [
                              {
                                "$ref": "#/components/schemas/ArchiveId"
                              },
                              {
                                "$ref": "#/components/schemas/TankoubonId"
                              }
                            ],
                            "type": "string"
                          },
                          "type": "array",
                          "uniqueItems": true
                        },
                        "full_data": {
                          "items": {
                            "$ref": "#/components/schemas/ArchiveMetadataJson"
                          },
                          "type": "array"
                        },
                        "id": {
                          "description": "ID of this Tankoubon.",
                          "type": "string"
                        },
                        "name": {
                          "description": "Name of this Tankoubon.",
                          "type": "string"
                        },
                        "progress": {
                          "description": "Reading progress (page number) for this Tankoubon.",
                          "type": "integer"
                        },
                        "summary": {
                          "type": "string"
                        },
                        "tags": {
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "total": {
                      "description": "The total amount of archives in this Tankoubon.",
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Tankoubon"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Error response"
          }
        },
        "summary": "Get a single Tankoubon with full detail",
        "tags": [
          "tankoubons"
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/tankoubons.rs:90"
      }
    },
    "/api/tankoubons/{id}/progress/{page}": {
      "put": {
        "description": "Tell the server which page of this Tankoubon you're currently reading, so that it updates its internal reading progression accordingly.\nThe page number is global across all Archives in the tank  (If a tank has two Archives with 20 and 25 pages, page 26 will be page 6 in Archive #2).\n⚠ If the server is configured to use clientside progress tracking, this API call will return an error!\nMake sure to check using `/api/info` whether the server tracks reading progression or not before calling this endpoint.",
        "operationId": "updateTankoubonProgress",
        "parameters": [
          {
            "description": "ID of the Tankoubon to update.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/TankoubonId"
            }
          },
          {
            "description": "Global 1-indexed page number to update the reading progress to. Must be a positive integer.",
            "in": "path",
            "name": "page",
            "required": true,
            "schema": {
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "id": "TANK_1589141306",
                  "lastreadtime": 123943543,
                  "operation": "update_tank_progress",
                  "page": 5,
                  "success": 1
                },
                "schema": {
                  "properties": {
                    "id": {
                      "$ref": "#/components/schemas/TankoubonId",
                      "description": "ID of the tankoubon updated"
                    },
                    "lastreadtime": {
                      "description": "Last read time in epoch seconds",
                      "type": "integer"
                    },
                    "operation": {
                      "description": "Name of the operation",
                      "enum": [
                        "update_tank_progress"
                      ],
                      "type": "string"
                    },
                    "page": {
                      "description": "Updated reading progress",
                      "type": "integer"
                    },
                    "success": {
                      "description": "Returns 1 if successful, else 0",
                      "enum": [
                        0,
                        1
                      ],
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Success response"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Server-side Progress Tracking is disabled on this instance.",
                  "operation": "update_tank_progress",
                  "success": 0
                },
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Generic error response"
          },
          "401": {
            "content": {
              "application/json": {
                "example": {
                  "error": "This operation requires authentication.",
                  "operation": "update_tank_progress",
                  "success": 0
                },
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Authentication required when authprogress is enabled"
          }
        },
        "summary": "Update Tankoubon reading progression",
        "tags": [
          "tankoubons"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/tankoubons.rs:95",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/tankoubons/{id}/stamps/{page}": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `tank_stamps_by_page` in crates/lanrurugi-api/src/stamps.rs:58.",
        "operationId": "tankStampsByPage",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/stamps.rs:52."
          }
        },
        "summary": "tank_stamps_by_page",
        "tags": [
          "stamps"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/stamps.rs:52",
        "x-lanrurugi-undocumented": true
      },
      "put": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `tank_add_stamp` in crates/lanrurugi-api/src/stamps.rs:72.",
        "operationId": "tankAddStamp",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/stamps.rs:52."
          }
        },
        "summary": "tank_add_stamp",
        "tags": [
          "stamps"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/stamps.rs:52",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/tankoubons/{id}/thumbnail": {
      "get": {
        "description": "Get the cover thumbnail for a given Tankoubon.  By default, the thumbnail is sourced from the first page of the first archive.  This endpoint will return a placeholder image if it doesn't already exist.  If you want to queue generation of the thumbnail in the background,  you can use the no_fallback query parameter. ",
        "operationId": "getTankoubonThumbnail",
        "parameters": [
          {
            "description": "Disables the placeholder image, queues the thumbnail for extraction and returns a JSON with code 202. This parameter does nothing if the image already exists. (You will get the image with code 200 no matter what)",
            "in": "query",
            "name": "no_fallback",
            "required": false,
            "schema": {
              "enum": [
                "true",
                "false"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/octet-stream": {
                "schema": {
                  "format": "binary",
                  "type": "string"
                }
              }
            },
            "description": "If the thumbnail was already extracted, you get it directly."
          },
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MinionJobResponse"
                }
              }
            },
            "description": "The thumbnail is queued for extraction. Use `/api/minion/:jobid` to track when your thumbnail is ready."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Error response"
          }
        },
        "summary": "Get Tankoubon thumbnail",
        "tags": [
          "tankoubons"
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/tankoubons.rs:91"
      },
      "put": {
        "description": "Set the cover thumbnail for the given Tankoubon using a global page number that spans all archives in the tank (in order). The global page is translated to the correct archive and local page automatically.",
        "operationId": "updateTankoubonThumbnail",
        "parameters": [
          {
            "description": "Global 1-indexed page number across all archives in the tankoubon. Page 1 is the first page of the first archive, and so on.",
            "in": "query",
            "name": "page",
            "required": true,
            "schema": {
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "new_thumbnail": {
                      "description": "Path to the new thumbnail file",
                      "type": "string"
                    },
                    "operation": {
                      "enum": [
                        "update_tankoubon_thumbnail"
                      ],
                      "type": "string"
                    },
                    "success": {
                      "enum": [
                        0,
                        1
                      ],
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Tankoubon thumbnail updated"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Error response"
          }
        },
        "summary": "Update Tankoubon thumbnail",
        "tags": [
          "tankoubons"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/tankoubons.rs:91",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/tankoubons/{id}/{archive}": {
      "delete": {
        "operationId": "removeFromTankoubon",
        "parameters": [
          {
            "description": "ID of the Tankoubon to update.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/TankoubonId"
            }
          },
          {
            "description": "ID of the Archive to remove.",
            "in": "path",
            "name": "archive",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/ArchiveId"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Result"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Error response"
          },
          "423": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "The Tankoubon is currently locked for modification"
          }
        },
        "summary": "Remove an Archive from a Tankoubon",
        "tags": [
          "tankoubons"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/tankoubons.rs:99",
        "x-lanrurugi-token-role": "admin"
      },
      "put": {
        "description": "Append an archive at the final position of a Tankoubon.",
        "operationId": "addToTankoubon",
        "parameters": [
          {
            "description": "ID of the Tankoubon to update.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/TankoubonId"
            }
          },
          {
            "description": "ID of the Archive to append.",
            "in": "path",
            "name": "archive",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/ArchiveId"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Success response"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Error response"
          },
          "423": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "The Tankoubon is currently locked for modification"
          }
        },
        "summary": "Add an Archive to a Tankoubon",
        "tags": [
          "tankoubons"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/tankoubons.rs:99",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/tempfolder": {
      "delete": {
        "description": "Cleans the server's temporary folder.",
        "operationId": "cleanTempfolder",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "error": null,
                    "newsize": 0,
                    "operation": "cleantemp",
                    "success": 1
                  },
                  "properties": {
                    "error": {
                      "description": "Error message if something went wrong during cleanup. (The call will still return 200)",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "newsize": {
                      "description": "Current size of the temporary folder post-cleanup.",
                      "type": "integer"
                    },
                    "operation": {
                      "enum": [
                        "cleantemp"
                      ],
                      "type": "string"
                    },
                    "success": {
                      "enum": [
                        0,
                        1
                      ],
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Cleanup result"
          }
        },
        "summary": "Clean the Temporary Folder",
        "tags": [
          "misc"
        ],
        "x-lanrurugi-schema-source": "legacy",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/upload.rs:49",
        "x-lanrurugi-token-role": "admin"
      }
    },
    "/api/theme": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/settings.rs:160), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nissue #92: this response also carries `language` now, not just `theme` — `useApplyTheme`/\n`useApplySettingsLanguage` (`apps/frontend/src/theme.ts`/`i18n/index.ts`) both used to read\ntheir respective field off the full, auth-gated `GET /settings` (which 401s pre-login, since\nthat response also carries the API key and other genuinely secret fields that can't be made\npublic) — every page rendered before a session exists (the Login page's own bespoke\n`usePublicTheme` fallback aside) hit that 401, and the *global* 401 handler in `client.ts`\nforce-navigates to `/login` on every single one regardless of whether the calling hook itself\nalready had a fallback ready, defeating issue #92's own \"stay on the page, don't redirect\"\nrequirement for anything rendered while logged out (the new 404 page very much included —\nlive-reported as a real double-navigation: the 404 content flashes, then `/login` anyway).\n`theme`/`language` are the only two fields anything needs before a session exists, and neither\nis remotely secret, so folding `language` into this already-public endpoint is simpler than\nstanding up a whole second public settings surface for one more field. The response also\ncarries `admin_theme` — always the administrator's own `theme`, regardless of guest-eligibility\n— so the Login page can render with the admin theme even when guest mode is on and the guest\ntheme is a different one.",
        "operationId": "getTheme",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/settings.rs:70."
          }
        },
        "security": [],
        "summary": "issue #92: this response also carries `language` now, not just `theme` — `useApplyTheme`/",
        "tags": [
          "settings"
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/settings.rs:70",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/token/refresh": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/login.rs:369), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\n`POST /token/refresh` — no legacy equivalent. Redeems the refresh cookie for a fresh access\ntoken + rotated refresh cookie (see `lanrurugi_storage::refresh_tokens::rotate`'s own docs for\nthe rotation/reuse-detection semantics). The frontend calls this transparently on a 401 from\nany other endpoint (`apps/frontend/src/api/client.ts`), before ever redirecting to `/login`.",
        "operationId": "refresh",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/login.rs:40."
          }
        },
        "security": [],
        "summary": "`POST /token/refresh` — no legacy equivalent",
        "tags": [
          "login"
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/login.rs:40",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/tokens": {
      "get": {
        "description": "Every issued token's metadata. The token secret itself is **never** included: it is returned\nexactly once, by the creation call below, and is not stored in a recoverable form.\n\nRequires a session-cookie login.\n",
        "operationId": "listTokens",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/ApiToken"
                  },
                  "type": "array"
                }
              }
            },
            "description": "The issued tokens."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "summary": "List API tokens",
        "tags": [
          "api_tokens"
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/api_tokens.rs:33"
      },
      "post": {
        "description": "Issues a new token and returns its secret — **the only time it is ever returned**. Store it\nthen; it cannot be recovered afterwards, only replaced.\n\nThe expiry is computed from the server's own clock, not from a client-supplied timestamp, so a\nclient with a skewed clock cannot mint a token that outlives what it asked for.\n",
        "operationId": "createToken",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "expires_in_secs": {
                    "description": "Seconds from issuance until the token stops working. Absent or `null` means a token that does not expire.",
                    "format": "int64",
                    "type": "integer"
                  },
                  "name": {
                    "description": "A human-readable label. Must not be empty.",
                    "type": "string"
                  },
                  "role": {
                    "default": "admin",
                    "description": "Defaults to `admin`, so a client that does not know about roles gets the pre-role-system behaviour rather than being silently downgraded.",
                    "enum": [
                      "admin",
                      "guest"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "name"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/OperationResponse"
                    },
                    {
                      "properties": {
                        "data": {
                          "allOf": [
                            {
                              "$ref": "#/components/schemas/ApiToken"
                            },
                            {
                              "properties": {
                                "token": {
                                  "description": "The token secret. Returned here and nowhere else.",
                                  "type": "string"
                                }
                              },
                              "type": "object"
                            }
                          ]
                        }
                      },
                      "type": "object"
                    }
                  ]
                }
              }
            },
            "description": "The token, including its secret under `data.token`."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "The name was empty."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "summary": "Create an API token",
        "tags": [
          "api_tokens"
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/api_tokens.rs:33"
      }
    },
    "/api/tokens/{id}": {
      "delete": {
        "description": "Permanently revokes a token. Anything still using it starts failing immediately; the token\ncannot be un-revoked, only replaced.\n\nRequires a session-cookie login.\n",
        "operationId": "deleteToken",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "Revoked."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "No such token."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "summary": "Revoke an API token",
        "tags": [
          "api_tokens"
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/api_tokens.rs:34"
      },
      "patch": {
        "description": "Changes a token's label. Requires a session-cookie login.",
        "operationId": "renameToken",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "name": {
                    "type": "string"
                  }
                },
                "required": [
                  "name"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/OperationResponse"
                    },
                    {
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/ApiToken"
                        }
                      },
                      "type": "object"
                    }
                  ]
                }
              }
            },
            "description": "The renamed token."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationResponse"
                }
              }
            },
            "description": "No such token."
          }
        },
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "summary": "Rename an API token",
        "tags": [
          "api_tokens"
        ],
        "x-lanrurugi-schema-source": "sidecar",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/api_tokens.rs:34"
      }
    },
    "/api/translation/budget": {
      "put": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `update_budget` in crates/lanrurugi-api/src/translation_settings.rs:335.",
        "operationId": "updateBudget",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/translation_settings.rs:40."
          }
        },
        "summary": "update_budget",
        "tags": [
          "translation_settings"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/translation_settings.rs:40",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/translation/settings": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `get_translation_settings` in crates/lanrurugi-api/src/translation_settings.rs:118.",
        "operationId": "getTranslationSettings",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/translation_settings.rs:35."
          }
        },
        "summary": "get_translation_settings",
        "tags": [
          "translation_settings"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/translation_settings.rs:35",
        "x-lanrurugi-undocumented": true
      },
      "put": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `update_translation_settings` in crates/lanrurugi-api/src/translation_settings.rs:172.",
        "operationId": "updateTranslationSettings",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/translation_settings.rs:35."
          }
        },
        "summary": "update_translation_settings",
        "tags": [
          "translation_settings"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/translation_settings.rs:35",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/translation/usage": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/translation_settings.rs:277), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nCurrent consumption at the four granularities FR-014 requires.\n\nReturns a zeroed result rather than an error when no metered backend is configured — a\nlocally-hosted backend simply has no usage to report.",
        "operationId": "getTranslationUsage",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/translation_settings.rs:39."
          }
        },
        "summary": "Current consumption at the four granularities FR-014 requires",
        "tags": [
          "translation_settings"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/translation_settings.rs:39",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/version": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `get_version` in crates/lanrurugi-api/src/version.rs:286.",
        "operationId": "getVersion",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/version.rs:53."
          }
        },
        "security": [],
        "summary": "get_version",
        "tags": [
          "version"
        ],
        "x-lanrurugi-guest-visible": true,
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/version.rs:53",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/volumes/{id}/font-pattern": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `get_font_pattern` in crates/lanrurugi-api/src/font_pattern.rs:35.",
        "operationId": "getFontPattern",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/font_pattern.rs:25."
          }
        },
        "summary": "get_font_pattern",
        "tags": [
          "font_pattern"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/font_pattern.rs:25",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/volumes/{id}/font-pattern/reset": {
      "post": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `reset_font_pattern` in crates/lanrurugi-api/src/font_pattern.rs:52.",
        "operationId": "resetFontPattern",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/font_pattern.rs:24."
          }
        },
        "summary": "reset_font_pattern",
        "tags": [
          "font_pattern"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/font_pattern.rs:24",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/volumes/{id}/terminology-glossary": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `get_glossary` in crates/lanrurugi-api/src/terminology_glossary.rs:38.",
        "operationId": "getGlossary",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/terminology_glossary.rs:27."
          }
        },
        "summary": "get_glossary",
        "tags": [
          "terminology_glossary"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/terminology_glossary.rs:27",
        "x-lanrurugi-undocumented": true
      }
    },
    "/api/volumes/{id}/terminology-glossary/{term}": {
      "delete": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `delete_entry` in crates/lanrurugi-api/src/terminology_glossary.rs:139.",
        "operationId": "deleteEntry",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/terminology_glossary.rs:28."
          }
        },
        "summary": "delete_entry",
        "tags": [
          "terminology_glossary"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/terminology_glossary.rs:28",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      },
      "put": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe note below is the handler's own documentation comment (crates/lanrurugi-api/src/terminology_glossary.rs:59), quoted verbatim. It is written for maintainers of this codebase and may reference internal identifiers.\n\n---\n\nEdits a single entry. Takes effect on the next translation request (FR-007d).",
        "operationId": "updateEntry",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/terminology_glossary.rs:28."
          }
        },
        "summary": "Edits a single entry",
        "tags": [
          "terminology_glossary"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/terminology_glossary.rs:28",
        "x-lanrurugi-token-role": "admin",
        "x-lanrurugi-undocumented": true
      }
    },
    "/health": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `health` in crates/lanrurugi-api/src/health.rs:68.",
        "operationId": "health",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/health.rs:43."
          }
        },
        "security": [],
        "summary": "health",
        "tags": [
          "health"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/health.rs:43",
        "x-lanrurugi-undocumented": true
      }
    },
    "/opensearch.xml": {
      "get": {
        "description": "Not yet described in this reference. This endpoint is listed because the server registers it, but its request and response shapes have not been written down yet.\n\nThe handler is `opensearch_xml` in crates/lanrurugi-api/src/opensearch.rs:74.",
        "operationId": "opensearchXml",
        "responses": {
          "200": {
            "description": "The endpoint responded. Its exact shape is not documented yet — see the handler at crates/lanrurugi-api/src/opensearch.rs:25."
          }
        },
        "security": [],
        "summary": "opensearch_xml",
        "tags": [
          "opensearch"
        ],
        "x-lanrurugi-schema-source": "none",
        "x-lanrurugi-source": "crates/lanrurugi-api/src/opensearch.rs:25",
        "x-lanrurugi-undocumented": true
      }
    }
  },
  "security": [
    {
      "bearerAuth": []
    },
    {
      "sessionCookie": []
    }
  ],
  "servers": [
    {
      "description": "LANrurugi's default local address. Replace this with your own instance's address — see the API testing guide.",
      "url": "http://localhost:3000"
    }
  ],
  "tags": [
    {
      "name": "activity"
    },
    {
      "name": "api_tokens"
    },
    {
      "name": "archive_split"
    },
    {
      "description": "Everything dealing with Archives.",
      "name": "archives"
    },
    {
      "name": "auth_bridge"
    },
    {
      "name": "bench"
    },
    {
      "name": "bookmarks"
    },
    {
      "description": "Endpoints related to Categories.",
      "name": "categories"
    },
    {
      "description": "Database management APIs.",
      "name": "database"
    },
    {
      "name": "download_queue"
    },
    {
      "name": "duplicates"
    },
    {
      "name": "font_pattern"
    },
    {
      "name": "health"
    },
    {
      "name": "jobs"
    },
    {
      "name": "login"
    },
    {
      "name": "logs"
    },
    {
      "description": "Minion Job Queue APIs.",
      "name": "minion"
    },
    {
      "description": "Other APIs that don't fit a dedicated theme.",
      "name": "misc"
    },
    {
      "description": "Endpoints related to OPDS catalog generation and serving.",
      "name": "opds"
    },
    {
      "name": "opensearch"
    },
    {
      "name": "plugin_wizard"
    },
    {
      "description": "APIs to list and execute Plugins.",
      "name": "plugins"
    },
    {
      "name": "preferences"
    },
    {
      "name": "recommend"
    },
    {
      "name": "scripts"
    },
    {
      "description": "Perform searches.",
      "name": "search"
    },
    {
      "name": "sessions"
    },
    {
      "name": "settings"
    },
    {
      "description": "Shinobu Filewatcher APIs.",
      "name": "shinobu"
    },
    {
      "description": "Stamps.",
      "name": "stamps"
    },
    {
      "name": "subscriptions"
    },
    {
      "name": "tankoubon_grouping"
    },
    {
      "description": "Endpoints related to Tankoubons.",
      "name": "tankoubons"
    },
    {
      "name": "terminology_glossary"
    },
    {
      "name": "translation"
    },
    {
      "name": "translation_settings"
    },
    {
      "name": "version"
    }
  ]
}
