{
  "components": {
    "parameters": {
      "IdempotencyKey": {
        "description": "Optional. Send a new unique value (for example a UUID) with each request you may retry. A retry with the same key returns the first attempt's response (header Idempotent-Replayed: true) instead of creating again, including after a 503 whose write completed later. Keys are per app and kept ten minutes.",
        "in": "header",
        "name": "Idempotency-Key",
        "required": false,
        "schema": {
          "type": "string"
        }
      },
      "path.field_id": {
        "description": "Field identifier",
        "in": "path",
        "name": "field_id",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "path.filename": {
        "description": "Media filename",
        "in": "path",
        "name": "filename",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "path.job_id": {
        "in": "path",
        "name": "job_id",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "path.note_type_id": {
        "description": "Note type ID",
        "in": "path",
        "name": "note_type_id",
        "required": true,
        "schema": {
          "type": "integer"
        }
      },
      "path.provider_id": {
        "in": "path",
        "name": "provider_id",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "path.template_id": {
        "description": "Template identifier",
        "in": "path",
        "name": "template_id",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "query.cursor": {
        "description": "Opaque next_cursor from the previous response. Omit to start at page one; malformed or empty cursors return 400.",
        "in": "query",
        "name": "cursor",
        "required": false,
        "schema": {
          "type": "string"
        }
      },
      "query.distinct_on": {
        "description": "One row per distinct value of this field: the first in `order`, like PostgreSQL's DISTINCT ON. distinct_on=card_id&order=id:desc on reviews is each card's latest review. On cards, notes and reviews it takes a column field, and every where clause must be on a column too.",
        "in": "query",
        "name": "distinct_on",
        "required": false,
        "schema": {
          "type": "string"
        }
      },
      "query.include": {
        "description": "Extra parts: total adds `total`, how many rows the query matches across all pages. With limit=0, the count alone.",
        "in": "query",
        "name": "include",
        "required": false,
        "schema": {
          "type": "string"
        }
      },
      "query.limit": {
        "description": "Maximum results in this response; 0 for none (with include=total, the count alone). Omit to return all matches; no fixed upper cap.",
        "in": "query",
        "name": "limit",
        "required": false,
        "schema": {
          "minimum": 0.0,
          "type": "integer"
        }
      },
      "query.select": {
        "description": "Comma-separated fields to return, e.g. `id,name`. Arrays: `fields[]` returns every element, `fields[].name` one property of each, `fields[].(name,value)` several, and `fields[name in [\"Front\",\"Back\"]]` only the elements whose `name` is listed.",
        "in": "query",
        "name": "select",
        "required": false,
        "schema": {
          "type": "string"
        }
      },
      "query.shape": {
        "description": "object (default): each item is an object with the selected fields. scalar: with exactly one selected field, each item is that field's bare value.",
        "in": "query",
        "name": "shape",
        "required": false,
        "schema": {
          "default": "object",
          "enum": [
            "object",
            "scalar"
          ],
          "type": "string"
        }
      },
      "query.where": {
        "description": "Filter clauses (can specify multiple)",
        "in": "query",
        "name": "where",
        "required": false,
        "schema": {
          "items": {
            "type": "string"
          },
          "type": "array"
        }
      }
    },
    "responses": {
      "Error": {
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorBody"
            }
          }
        },
        "description": "An error: `detail` says what went wrong; on a 503, `reason` says why Anki can't answer now"
      },
      "ValidationFailed": {
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/HTTPValidationError"
            }
          }
        },
        "description": "Validation Error"
      }
    },
    "schemas": {
      "AccessChanged": {
        "additionalProperties": false,
        "description": "`access.changed`: fetch GET /v1/capabilities again.",
        "properties": {
          "ts": {
            "type": "integer"
          },
          "type": {
            "enum": [
              "access.changed"
            ],
            "type": "string"
          }
        },
        "required": [
          "type",
          "ts"
        ],
        "type": "object"
      },
      "AccessRequest": {
        "additionalProperties": false,
        "description": "What a website or program may suggest when it asks for access (6.124); you decide.",
        "properties": {
          "name": {
            "description": "The name for its app in Apps & keys. A website's default is its host; a program's is shown as the name it gives itself, which Tsunagi can't check.",
            "type": "string"
          },
          "role": {
            "description": "The role to preselect in the prompt, by id or name (`read_only`, `Read-only`); Default otherwise.",
            "type": "string"
          }
        },
        "type": "object"
      },
      "ActionInfo": {
        "properties": {
          "backup": {
            "default": false,
            "description": "Tsunagi backs up the collection before each run (destructive actions that change it outside undo)",
            "type": "boolean"
          },
          "description": {
            "type": "string"
          },
          "impact": {
            "description": "How much running it by mistake could matter: read (changes nothing), undoable (can be put back), or destructive (can't)",
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "params": {
            "additionalProperties": {
              "$ref": "#/components/schemas/ActionParam"
            },
            "type": "object"
          },
          "shows_ui": {
            "description": "Shows a progress window or message on the computer running Anki",
            "type": "boolean"
          },
          "status": {
            "description": "allowed, disabled (the user has not enabled it in Tsunagi's settings), not_permitted (the caller's role lacks it) or unsupported",
            "type": "string"
          },
          "title": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "title",
          "description",
          "impact",
          "params",
          "shows_ui",
          "status"
        ],
        "type": "object"
      },
      "ActionList": {
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/ActionInfo"
            },
            "type": "array"
          },
          "provider": {
            "type": "string"
          },
          "stats": {
            "type": "object"
          },
          "title": {
            "type": "string"
          },
          "unsupported": {
            "description": "Why the provider cannot run, if it cannot",
            "type": "string"
          }
        },
        "required": [
          "provider",
          "title",
          "items",
          "stats"
        ],
        "type": "object"
      },
      "ActionParam": {
        "properties": {
          "default": {},
          "description": {
            "type": "string"
          },
          "max": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "number"
              }
            ]
          },
          "min": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "number"
              }
            ]
          },
          "required": {
            "type": "boolean"
          },
          "type": {
            "description": "integer, number, boolean, string or dates (a list of YYYY-MM-DD)",
            "type": "string"
          }
        },
        "required": [
          "type",
          "description",
          "required"
        ],
        "type": "object"
      },
      "ActionResult": {
        "properties": {
          "result": {},
          "stats": {
            "type": "object"
          }
        },
        "required": [
          "stats"
        ],
        "type": "object"
      },
      "AddCardsRequest": {
        "additionalProperties": false,
        "description": "An empty body just opens the dialog; a note to prefill it names its deck and note type.\nFiles are stored and referenced in their fields as POST /v1/notes does it.",
        "properties": {
          "audio": {
            "anyOf": [
              {
                "items": {
                  "$ref": "#/components/schemas/NoteAttachment"
                },
                "type": "array"
              },
              {
                "$ref": "#/components/schemas/NoteAttachment"
              }
            ]
          },
          "deck_id": {
            "type": "integer"
          },
          "deck_name": {
            "type": "string"
          },
          "fields": {
            "additionalProperties": {
              "type": "string"
            },
            "type": "object"
          },
          "note_type_id": {
            "type": "integer"
          },
          "note_type_name": {
            "type": "string"
          },
          "picture": {
            "anyOf": [
              {
                "items": {
                  "$ref": "#/components/schemas/NoteAttachment"
                },
                "type": "array"
              },
              {
                "$ref": "#/components/schemas/NoteAttachment"
              }
            ]
          },
          "tags": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "video": {
            "anyOf": [
              {
                "items": {
                  "$ref": "#/components/schemas/NoteAttachment"
                },
                "type": "array"
              },
              {
                "$ref": "#/components/schemas/NoteAttachment"
              }
            ]
          }
        },
        "type": "object"
      },
      "AddCardsResult": {
        "properties": {
          "files": {
            "description": "The prefilled note's files as stored, as POST /v1/notes reports them; null when it had none.",
            "items": {
              "$ref": "#/components/schemas/MediaStored"
            },
            "type": "array",
            "x-nullable": true
          },
          "note_id": {
            "type": "integer"
          },
          "stats": {
            "type": "object"
          }
        },
        "type": "object"
      },
      "AddonInfo": {
        "properties": {
          "ankiweb_id": {
            "description": "AnkiWeb id; null when the folder name isn't a number (not installed from AnkiWeb).",
            "type": "integer",
            "x-nullable": true
          },
          "compatible": {
            "description": "Whether it declares support for this Anki version",
            "type": "boolean"
          },
          "enabled": {
            "description": "Whether the add-on is enabled.",
            "type": "boolean"
          },
          "has_config": {
            "description": "Has settings (edited in Anki's add-on manager)",
            "type": "boolean"
          },
          "has_config_ui": {
            "description": "Registers its own settings dialog instead of Anki's JSON editor",
            "type": "boolean"
          },
          "homepage": {
            "description": "Homepage the add-on gives; null when none.",
            "type": "string",
            "x-nullable": true
          },
          "id": {
            "description": "Folder name; the AnkiWeb ID for AnkiWeb installs",
            "type": "string"
          },
          "name": {
            "description": "Display name; the folder name when the add-on gives none.",
            "type": "string"
          },
          "provider": {
            "description": "Id of the Tsunagi provider offering this add-on's actions; null when none. See GET /v1/addons/{provider}/actions",
            "type": "string",
            "x-nullable": true
          },
          "version": {
            "description": "The author's version label; null when none.",
            "type": "string",
            "x-nullable": true
          },
          "version_time": {
            "description": "When the installed version was published (AnkiWeb's upload time for AnkiWeb installs, otherwise the package's own timestamp), Unix seconds; 0 if unknown. Not when it was installed.",
            "type": "integer"
          }
        },
        "required": [
          "id",
          "name",
          "enabled",
          "compatible",
          "version_time",
          "has_config",
          "has_config_ui"
        ],
        "type": "object"
      },
      "AddonList": {
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/AddonInfo"
            },
            "type": "array"
          },
          "stats": {
            "type": "object"
          }
        },
        "required": [
          "items",
          "stats"
        ],
        "type": "object"
      },
      "AnswerEntry": {
        "additionalProperties": false,
        "properties": {
          "card_id": {
            "type": "integer"
          },
          "rating": {
            "maximum": 4.0,
            "minimum": 1.0,
            "type": "integer"
          }
        },
        "required": [
          "card_id",
          "rating"
        ],
        "type": "object"
      },
      "AttachmentRef": {
        "properties": {
          "filename": {
            "description": "The filename sent; null when none was.",
            "type": "string",
            "x-nullable": true
          },
          "kind": {
            "enum": [
              "audio",
              "video",
              "picture"
            ],
            "type": "string"
          },
          "position": {
            "description": "Zero-based position in that kind's list; 0 for one object.",
            "type": "integer"
          }
        },
        "required": [
          "kind",
          "position"
        ],
        "type": "object"
      },
      "BatchOpResult": {
        "properties": {
          "affected": {
            "type": "integer"
          },
          "op": {
            "type": "string"
          }
        },
        "required": [
          "op",
          "affected"
        ],
        "type": "object"
      },
      "BatchRequest": {
        "additionalProperties": false,
        "properties": {
          "operations": {
            "items": {
              "type": "object"
            },
            "type": "array"
          }
        },
        "required": [
          "operations"
        ],
        "type": "object"
      },
      "BatchResult": {
        "properties": {
          "affected": {
            "type": "integer"
          },
          "results": {
            "items": {
              "$ref": "#/components/schemas/BatchOpResult"
            },
            "type": "array"
          },
          "stats": {
            "type": "object"
          }
        },
        "required": [
          "affected",
          "results",
          "stats"
        ],
        "type": "object"
      },
      "BrowseRequest": {
        "additionalProperties": false,
        "properties": {
          "query": {
            "description": "Anki search to run in the Browser",
            "type": "string"
          },
          "reorder": {
            "description": "{\"columnId\": ..., \"order\": \"ascending\"|\"descending\"}"
          }
        },
        "type": "object"
      },
      "BrowseResult": {
        "properties": {
          "card_ids": {
            "items": {
              "type": "integer"
            },
            "type": "array"
          },
          "stats": {
            "type": "object"
          }
        },
        "type": "object"
      },
      "CallerInfo": {
        "description": "Who a request counts as: the same in health and capabilities (6.86).",
        "properties": {
          "enabled": {
            "description": "False for an app turned off in settings: it is granted nothing",
            "type": "boolean"
          },
          "host": {
            "description": "The Host header as Tsunagi received it (useful behind a proxy)",
            "type": "string"
          },
          "key": {
            "description": "valid: the key belongs to `name`. unknown: a key was sent but matches no app, so it counts as no key. none: no key was sent",
            "enum": [
              "valid",
              "unknown",
              "none"
            ],
            "type": "string"
          },
          "name": {
            "description": "The app the request's key belongs to, or the No key row it counts as (no key, or a key that matches no app)",
            "type": "string"
          },
          "role": {
            "description": "The role that applies, by its display name",
            "type": "string"
          },
          "this_computer": {
            "description": "Counted as this computer: loopback peer and loopback Host",
            "type": "boolean"
          }
        },
        "required": [
          "name",
          "role",
          "enabled",
          "key",
          "this_computer",
          "host"
        ],
        "type": "object"
      },
      "Capabilities": {
        "properties": {
          "caller": {
            "allOf": [
              {
                "$ref": "#/components/schemas/CallerInfo"
              }
            ],
            "description": "Who Tsunagi took this request to be; statuses below are for this caller"
          },
          "features": {
            "additionalProperties": {
              "$ref": "#/components/schemas/CapabilityState"
            },
            "description": "Collection features that are not individual HTTP operations",
            "type": "object"
          },
          "operations": {
            "additionalProperties": {
              "$ref": "#/components/schemas/OperationCapability"
            },
            "description": "All native operations, keyed by METHOD /path",
            "type": "object"
          },
          "stats": {
            "additionalProperties": {
              "type": "number"
            },
            "type": "object"
          },
          "versions": {
            "$ref": "#/components/schemas/Versions"
          }
        },
        "required": [
          "versions",
          "caller",
          "operations",
          "features"
        ],
        "type": "object"
      },
      "CapabilityState": {
        "properties": {
          "reason": {
            "description": "Why it is disabled or unsupported, or what limits it; null when nothing does",
            "type": "string",
            "x-nullable": true
          },
          "setting": {
            "description": "Setting controlling this capability; null when none does",
            "type": "string",
            "x-nullable": true
          },
          "status": {
            "default": "available",
            "description": "Effective support after checking the running Anki and settings; request validation still applies",
            "enum": [
              "available",
              "disabled",
              "unsupported"
            ],
            "type": "string"
          }
        },
        "type": "object"
      },
      "CardAnswered": {
        "additionalProperties": false,
        "description": "`cards.answered`: a card was answered, in Anki or through either API; its new state.",
        "properties": {
          "anki": {
            "description": "Anki's own description of the change, for debugging; don't rely on it",
            "type": "object",
            "x-nullable": true
          },
          "app": {
            "description": "For by api: the app that made it; null otherwise",
            "type": "string",
            "x-nullable": true
          },
          "by": {
            "description": "api: made through either API; ui: made in Anki; null when unknown",
            "enum": [
              "ui",
              "api"
            ],
            "type": "string",
            "x-nullable": true
          },
          "card_id": {
            "type": "integer"
          },
          "due": {
            "description": "The card's new due value (see /v1/cards).",
            "type": "integer"
          },
          "interval": {
            "description": "The card's new interval, days.",
            "type": "integer"
          },
          "memory_state": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FsrsMemoryState"
              }
            ],
            "description": "FSRS memory state; null for cards FSRS hasn't scheduled",
            "x-nullable": true
          },
          "queue": {
            "description": "The card's new queue.",
            "type": "integer",
            "x-values": {
              "-1": "suspended",
              "-2": "sibling_buried",
              "-3": "manually_buried",
              "0": "new",
              "1": "learning",
              "2": "review",
              "3": "day_learning",
              "4": "preview"
            }
          },
          "rating": {
            "description": "The answer button.",
            "type": "integer",
            "x-values": {
              "1": "again",
              "2": "hard",
              "3": "good",
              "4": "easy"
            }
          },
          "seq": {
            "description": "Its number in the session; the SSE `id:` is `<session_id>:<seq>`.",
            "type": "integer"
          },
          "session_id": {
            "description": "The server session; a new one after a restart or profile switch.",
            "type": "string"
          },
          "ts": {
            "description": "When it was sent, Unix milliseconds.",
            "type": "integer"
          },
          "type": {
            "description": "The message's name, also its SSE `event:`.",
            "type": "string"
          }
        },
        "required": [
          "type",
          "seq",
          "session_id",
          "ts",
          "card_id",
          "rating",
          "interval",
          "due",
          "queue"
        ],
        "type": "object"
      },
      "CardIdRequest": {
        "additionalProperties": false,
        "properties": {
          "card_id": {
            "type": "integer"
          }
        },
        "required": [
          "card_id"
        ],
        "type": "object"
      },
      "CardIds": {
        "additionalProperties": false,
        "description": "Base body for the batch scheduling verbs.",
        "properties": {
          "card_ids": {
            "description": "A card id sent twice counts once; a missing card is skipped.",
            "items": {
              "type": "integer"
            },
            "type": "array"
          }
        },
        "required": [
          "card_ids"
        ],
        "type": "object"
      },
      "CardRow": {
        "description": "A card row. Without `select`, every field is present; with `select`, only those it names. With `shape=scalar`, each item is the selected field's bare value instead.",
        "properties": {
          "answer": {
            "description": "The card's back, rendered as HTML.",
            "type": "string",
            "x-from": [
              "notes",
              "note_types",
              "decks"
            ]
          },
          "buried": {
            "description": "True when the card is buried, by a sibling or by the user (queue -2 or -3).",
            "type": "boolean"
          },
          "css": {
            "description": "The note type's styling.",
            "type": "string",
            "x-from": [
              "note_types"
            ]
          },
          "custom_data": {
            "description": "A JSON object string that custom scheduling code stores on the card; \"\" when none.",
            "type": "string"
          },
          "decay": {
            "description": "FSRS decay parameter used for the card; null until FSRS has scheduled it.",
            "type": "number",
            "x-nullable": true
          },
          "deck_id": {
            "description": "Id of the deck the card is in now; a filtered deck's id while it sits in one.",
            "type": "integer"
          },
          "deck_name": {
            "description": "Full name of the deck in deck_id, with \"::\" between levels.",
            "type": "string",
            "x-from": [
              "decks"
            ]
          },
          "desired_retention": {
            "description": "Desired retention (0-1) in effect when the card was last scheduled; null if none recorded.",
            "type": "number",
            "x-nullable": true
          },
          "due": {
            "description": "Anki's stored value, not days from today. New: position in the new queue. Learning: epoch seconds. Review: days since the collection was created. For cards due on a day, search `prop:due=1` (days from today: 0 today, -1 yesterday); `is:due` for cards due now.",
            "type": "integer"
          },
          "ease_factor": {
            "description": "SM-2 ease in permille (2500 = 250%); 0 until the card graduates. FSRS doesn't use it.",
            "type": "integer"
          },
          "fields": {
            "description": "The note's fields, in note type order.",
            "items": {
              "$ref": "#/components/schemas/NoteField"
            },
            "type": "array",
            "x-from": [
              "notes",
              "note_types"
            ]
          },
          "flag": {
            "description": "The card's flag colour.",
            "type": "integer",
            "x-values": {
              "0": "none",
              "1": "red",
              "2": "orange",
              "3": "green",
              "4": "blue",
              "5": "pink",
              "6": "turquoise",
              "7": "purple"
            }
          },
          "id": {
            "description": "Card id: its creation time in epoch milliseconds.",
            "type": "integer"
          },
          "interval": {
            "description": "Current interval in days; 0 for new and learning cards.",
            "type": "integer"
          },
          "lapses": {
            "description": "How many times the card was forgotten (Again on a review card).",
            "type": "integer"
          },
          "last_review_time": {
            "description": "When the card was last answered, Unix seconds; null if never, or before Anki recorded it.",
            "type": "integer",
            "x-nullable": true
          },
          "memory_state": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FsrsMemoryState"
              }
            ],
            "description": "FSRS memory state; null until FSRS has scheduled the card.",
            "x-nullable": true
          },
          "modified": {
            "description": "Last modified, Unix seconds.",
            "type": "integer"
          },
          "next_reviews": {
            "description": "Next interval for each answer button, Again to Easy, as Anki shows them (e.g. \"10m\", \"4d\"); null if Anki couldn't work them out.",
            "items": {
              "type": "string"
            },
            "type": "array",
            "x-nullable": true
          },
          "note_id": {
            "description": "Id of the note the card belongs to.",
            "type": "integer"
          },
          "note_type_name": {
            "description": "Name of the card's note type.",
            "type": "string",
            "x-from": [
              "note_types"
            ]
          },
          "original_deck_id": {
            "description": "Home deck id while the card is in a filtered deck; 0 otherwise.",
            "type": "integer"
          },
          "original_due": {
            "description": "The card's due value in its home deck while it is in a filtered deck; 0 otherwise.",
            "type": "integer"
          },
          "original_position": {
            "description": "Where the card was in the new queue before it was first studied; null if Anki didn't record it.",
            "type": "integer",
            "x-nullable": true
          },
          "question": {
            "description": "The card's front, rendered as HTML.",
            "type": "string",
            "x-from": [
              "notes",
              "note_types",
              "decks"
            ]
          },
          "queue": {
            "description": "The queue the card is in, which also says what `due` means: new (a position), learning and preview (epoch seconds), review and day_learning (a day number; day_learning is learning whose next step is on a later day); suspended, sibling_buried (buried automatically with a sibling) and manually_buried keep `due` from before.",
            "type": "integer",
            "x-values": {
              "-1": "suspended",
              "-2": "sibling_buried",
              "-3": "manually_buried",
              "0": "new",
              "1": "learning",
              "2": "review",
              "3": "day_learning",
              "4": "preview"
            }
          },
          "reps": {
            "description": "How many times the card has been answered.",
            "type": "integer"
          },
          "retrievability": {
            "description": "FSRS estimate (0-1) that the card would be recalled now; null if FSRS hasn't scored it.",
            "type": "number",
            "x-nullable": true
          },
          "steps_left": {
            "description": "Learning steps the card still has to pass before it graduates, the current one included; only meaningful while the card is learning or relearning.",
            "type": "integer"
          },
          "suspended": {
            "description": "True when the card is suspended (queue -1).",
            "type": "boolean"
          },
          "template_index": {
            "description": "Which template made the card, from 0; on a cloze note type, the cloze number minus 1.",
            "type": "integer"
          },
          "type": {
            "description": "The card's scheduling stage.",
            "type": "integer",
            "x-values": {
              "0": "new",
              "1": "learning",
              "2": "review",
              "3": "relearning"
            }
          },
          "usn": {
            "description": "Update sequence number for syncing; -1 means changed since the last sync.",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "CardTemplate": {
        "properties": {
          "afmt": {
            "description": "Back template.",
            "type": "string"
          },
          "bafmt": {
            "description": "Back template for the browser's Answer column; \"\" uses afmt.",
            "type": "string"
          },
          "bqfmt": {
            "description": "Front template for the browser's Question column; \"\" uses qfmt.",
            "type": "string"
          },
          "deck_override_id": {
            "description": "Deck that new cards from this template go to, overriding the chosen deck; null when none.",
            "type": "integer",
            "x-nullable": true
          },
          "index": {
            "description": "Template position, from 0; cards record it as their template_index.",
            "type": "integer"
          },
          "name": {
            "description": "Template name.",
            "type": "string"
          },
          "qfmt": {
            "description": "Front template.",
            "type": "string"
          }
        },
        "required": [
          "name",
          "index"
        ],
        "type": "object"
      },
      "ChangeDeckRequest": {
        "additionalProperties": false,
        "description": "Base body for the batch scheduling verbs.",
        "properties": {
          "card_ids": {
            "description": "A card id sent twice counts once; a missing card is skipped.",
            "items": {
              "type": "integer"
            },
            "type": "array"
          },
          "deck_id": {
            "type": "integer"
          },
          "deck_name": {
            "type": "string"
          }
        },
        "required": [
          "card_ids"
        ],
        "type": "object"
      },
      "Close": {
        "additionalProperties": false,
        "description": "`close`: the server ended the stream (the last message).",
        "properties": {
          "reason": {
            "enum": [
              "profile_closed",
              "shutdown",
              "auth",
              "timeout",
              "max_events"
            ],
            "type": "string"
          }
        },
        "required": [
          "reason"
        ],
        "type": "object"
      },
      "CollectionActionResult": {
        "description": "Result of a collection-level command that either worked or raised.",
        "properties": {
          "stats": {
            "type": "object"
          },
          "success": {
            "default": true,
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "CollectionHealth": {
        "properties": {
          "profile": {
            "description": "Open profile name; null when none is open",
            "type": "string",
            "x-nullable": true
          },
          "state": {
            "description": "ready; syncing; closed (no collection, e.g. during a full sync); busy (Anki did not answer a trivial read within a second). A 503 body carries the same value as reason",
            "enum": [
              "ready",
              "syncing",
              "closed",
              "busy"
            ],
            "type": "string"
          }
        },
        "required": [
          "state"
        ],
        "type": "object"
      },
      "CollectionMeta": {
        "description": "Collection-level facts a client can't read from any row: whether FSRS is\nenabled (one collection-wide switch - NOT per deck or per preset, even\nthough Anki's deck-options screen hosts the toggle), and the Anki version\nfor feature detection.",
        "properties": {
          "anki_version": {
            "type": "string"
          },
          "fsrs": {
            "type": "boolean"
          },
          "stats": {
            "type": "object"
          }
        },
        "required": [
          "fsrs",
          "anki_version"
        ],
        "type": "object"
      },
      "ComputeParamsRequest": {
        "additionalProperties": false,
        "properties": {
          "current_params": {
            "items": {
              "type": "number"
            },
            "type": "array"
          },
          "health_check": {
            "type": "boolean"
          },
          "ignore_revlogs_before_ms": {
            "type": "integer"
          },
          "num_of_relearning_steps": {
            "type": "integer"
          },
          "search": {
            "default": "",
            "type": "string"
          }
        },
        "type": "object"
      },
      "CurrentCard": {
        "description": "Null when no review is in progress.",
        "properties": {
          "answer": {
            "type": "string"
          },
          "buttons": {
            "items": {
              "type": "integer"
            },
            "type": "array"
          },
          "card_id": {
            "type": "integer"
          },
          "css": {
            "type": "string"
          },
          "deck_name": {
            "type": "string"
          },
          "field_order": {
            "type": "integer"
          },
          "fields": {
            "additionalProperties": {
              "type": "object"
            },
            "type": "object"
          },
          "next_reviews": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "note_type_name": {
            "type": "string"
          },
          "question": {
            "type": "string"
          },
          "template": {
            "type": "string"
          }
        },
        "required": [
          "card_id",
          "fields",
          "field_order",
          "question",
          "answer",
          "buttons",
          "note_type_name",
          "deck_name",
          "css",
          "template"
        ],
        "type": "object"
      },
      "CurrentCardResult": {
        "properties": {
          "card": {
            "$ref": "#/components/schemas/CurrentCard"
          },
          "review_active": {
            "default": false,
            "type": "boolean"
          },
          "stats": {
            "type": "object"
          }
        },
        "type": "object"
      },
      "DayChanged": {
        "additionalProperties": false,
        "description": "`day.changed`: Anki's day changed. Searches relative to today (`is:due`, `is:buried`,\n`rated:1`, `prop:due=0`) can now match other rows, though none was written: run them again.",
        "properties": {
          "seq": {
            "description": "Its number in the session; the SSE `id:` is `<session_id>:<seq>`.",
            "type": "integer"
          },
          "session_id": {
            "description": "The server session; a new one after a restart or profile switch.",
            "type": "string"
          },
          "ts": {
            "description": "When it was sent, Unix milliseconds.",
            "type": "integer"
          },
          "type": {
            "description": "The message's name, also its SSE `event:`.",
            "type": "string"
          }
        },
        "required": [
          "type",
          "seq",
          "session_id",
          "ts"
        ],
        "type": "object"
      },
      "DeckCounts": {
        "additionalProperties": false,
        "properties": {
          "deck_id": {
            "description": "The deck's id.",
            "type": "integer"
          },
          "learn_count": {
            "type": "integer"
          },
          "new_count": {
            "type": "integer"
          },
          "review_count": {
            "type": "integer"
          },
          "total_in_deck": {
            "type": "integer"
          }
        },
        "required": [
          "deck_id",
          "new_count",
          "learn_count",
          "review_count",
          "total_in_deck"
        ],
        "type": "object"
      },
      "DeckCreate": {
        "additionalProperties": false,
        "description": "Schema for creating a deck (\"::\" nesting allowed)",
        "properties": {
          "description": {
            "type": "string"
          },
          "name": {
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "DeckPatch": {
        "additionalProperties": false,
        "description": "Schema for patching a deck - all fields optional",
        "properties": {
          "browser_collapsed": {
            "type": "boolean"
          },
          "collapsed": {
            "type": "boolean"
          },
          "description": {
            "type": "string"
          },
          "desired_retention": {
            "type": "number"
          },
          "name": {
            "type": "string"
          },
          "preset_id": {
            "type": "integer"
          }
        },
        "type": "object"
      },
      "DeckPresetRow": {
        "description": "A deck preset row. Without `select`, every field is present; with `select`, only those it names. With `shape=scalar`, each item is the selected field's bare value instead.",
        "properties": {
          "id": {
            "description": "Preset id; 1 is the Default preset.",
            "type": "integer"
          },
          "name": {
            "description": "Preset name. Other keys are Anki's own settings, as Anki names them.",
            "type": "string"
          }
        },
        "type": "object"
      },
      "DeckRequest": {
        "additionalProperties": false,
        "description": "A deck by name or id (the id when both are sent).",
        "properties": {
          "deck_id": {
            "type": "integer"
          },
          "deck_name": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "DeckRow": {
        "description": "A deck row. Without `select`, every field is present; with `select`, only those it names. With `shape=scalar`, each item is the selected field's bare value instead.",
        "properties": {
          "browser_collapsed": {
            "description": "Whether the deck's subdecks are collapsed in the browser sidebar.",
            "type": "boolean"
          },
          "collapsed": {
            "description": "Whether the deck's subdecks are collapsed in the deck list.",
            "type": "boolean"
          },
          "description": {
            "description": "The deck's description, shown on its overview screen.",
            "type": "string"
          },
          "desired_retention": {
            "description": "This deck's FSRS desired retention (0-1), overriding its preset; null when unset or filtered.",
            "type": "number",
            "x-nullable": true
          },
          "dynamic": {
            "description": "Whether this is a filtered deck.",
            "type": "integer",
            "x-values": {
              "0": "normal",
              "1": "filtered"
            }
          },
          "extend_new": {
            "description": "Last amount entered in Custom Study's \"increase today's new card limit\"; 0 for filtered decks.",
            "type": "integer"
          },
          "extend_rev": {
            "description": "Last amount entered in Custom Study's \"increase today's review limit\"; 0 for filtered decks.",
            "type": "integer"
          },
          "id": {
            "description": "Deck id.",
            "type": "integer"
          },
          "learn_count": {
            "description": "Learning cards due today, subdecks included, as the deck list shows them.",
            "type": "integer"
          },
          "modified": {
            "description": "Last modified, Unix seconds.",
            "type": "integer"
          },
          "name": {
            "description": "Full deck name, with \"::\" between levels.",
            "type": "string"
          },
          "new_count": {
            "description": "New cards to study today, subdecks included, as the deck list shows them.",
            "type": "integer"
          },
          "new_limit": {
            "description": "This deck's own daily new card limit, overriding its preset; null when it has none.",
            "type": "integer",
            "x-nullable": true
          },
          "new_limit_today": {
            "description": "New card limit set for today only, overriding the others; null unless set for today.",
            "type": "integer",
            "x-nullable": true
          },
          "new_limit_used": {
            "description": "How much of today's new card limit is used: each new card studied adds 1, Custom Study's \"increase today's new card limit\" subtracts, so it can be negative.",
            "type": "integer"
          },
          "preset_id": {
            "description": "Id of the deck's options preset (see /v1/deck-presets); null for filtered decks.",
            "type": "integer",
            "x-nullable": true
          },
          "review_count": {
            "description": "Review cards due today, subdecks included, as the deck list shows them.",
            "type": "integer"
          },
          "review_limit": {
            "description": "This deck's own daily review limit, overriding its preset; null when it has none.",
            "type": "integer",
            "x-nullable": true
          },
          "review_limit_today": {
            "description": "Review limit set for today only, overriding the others; null unless set for today.",
            "type": "integer",
            "x-nullable": true
          },
          "review_limit_used": {
            "description": "How much of today's review limit is used: each review adds 1, Custom Study's \"increase today's review limit\" subtracts, so it can be negative.",
            "type": "integer"
          },
          "study_ms_today": {
            "description": "Time spent studying the deck today, in milliseconds.",
            "type": "integer"
          },
          "total_in_deck": {
            "description": "Cards in this deck itself, not counting subdecks.",
            "type": "integer"
          },
          "usn": {
            "description": "Update sequence number for syncing; -1 means changed since the last sync.",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "DecksCounts": {
        "additionalProperties": false,
        "description": "`decks.counts`: decks whose due counts changed, as /v1/decks rows count them.",
        "properties": {
          "decks": {
            "items": {
              "$ref": "#/components/schemas/DeckCounts"
            },
            "type": "array"
          },
          "seq": {
            "description": "Its number in the session; the SSE `id:` is `<session_id>:<seq>`.",
            "type": "integer"
          },
          "session_id": {
            "description": "The server session; a new one after a restart or profile switch.",
            "type": "string"
          },
          "ts": {
            "description": "When it was sent, Unix milliseconds.",
            "type": "integer"
          },
          "type": {
            "description": "The message's name, also its SSE `event:`.",
            "type": "string"
          }
        },
        "required": [
          "type",
          "seq",
          "session_id",
          "ts",
          "decks"
        ],
        "type": "object"
      },
      "DeletionResult": {
        "properties": {
          "affected_ids": {
            "items": {
              "type": "integer"
            },
            "type": "array"
          },
          "stats": {
            "type": "object"
          },
          "success": {
            "type": "boolean"
          }
        },
        "required": [
          "success",
          "affected_ids",
          "stats"
        ],
        "type": "object"
      },
      "DuplicateScopeOptions": {
        "additionalProperties": false,
        "description": "AnkiConnect's duplicateScopeOptions, with the same names and defaults.",
        "properties": {
          "check_all_note_types": {
            "default": false,
            "description": "Match notes of every note type, not just the candidate's.",
            "type": "boolean"
          },
          "check_children": {
            "default": false,
            "description": "Deck scope: also check the deck's subdecks.",
            "type": "boolean"
          },
          "deck_name": {
            "description": "Deck scope: check this deck instead of the note's own deck.",
            "type": "string"
          }
        },
        "type": "object"
      },
      "EaseEntry": {
        "additionalProperties": false,
        "properties": {
          "ease_factor": {
            "type": "integer"
          },
          "id": {
            "type": "integer"
          }
        },
        "required": [
          "id",
          "ease_factor"
        ],
        "type": "object"
      },
      "EmptyCardsNote": {
        "properties": {
          "card_ids": {
            "description": "The note's empty cards that are (or would be) deleted.",
            "items": {
              "type": "integer"
            },
            "type": "array"
          },
          "note_deleted": {
            "description": "The note goes too: all its cards were empty.",
            "type": "boolean"
          },
          "note_id": {
            "type": "integer"
          }
        },
        "required": [
          "note_id",
          "card_ids",
          "note_deleted"
        ],
        "type": "object"
      },
      "EmptyCardsRequest": {
        "additionalProperties": false,
        "description": "Body for POST /v1/cards:delete-empty (6.119), the options of Anki's Empty Cards window.",
        "properties": {
          "dry_run": {
            "default": false,
            "description": "Answer what would be deleted, and delete nothing.",
            "type": "boolean"
          },
          "keep_notes": {
            "default": true,
            "description": "Keep a note whose cards are all empty, with its first card, as Anki's \"Keep notes with no valid cards\" does; false deletes such notes.",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "EmptyCardsResult": {
        "properties": {
          "affected": {
            "description": "Cards deleted (with dry_run, that would be).",
            "type": "integer"
          },
          "dry_run": {
            "type": "boolean"
          },
          "notes": {
            "description": "Each note with cards deleted.",
            "items": {
              "$ref": "#/components/schemas/EmptyCardsNote"
            },
            "type": "array"
          },
          "notes_deleted": {
            "type": "integer"
          },
          "stats": {
            "type": "object"
          }
        },
        "required": [
          "notes",
          "affected",
          "notes_deleted",
          "dry_run",
          "stats"
        ],
        "type": "object"
      },
      "ErrorBody": {
        "properties": {
          "detail": {
            "description": "What went wrong, for the app to show; it can quote note content. Never parse it",
            "type": "string"
          },
          "reason": {
            "description": "On a 503, why Anki can't answer now: busy (it didn't answer in time), closed (no collection is open, e.g. during a full sync) or syncing; null otherwise",
            "enum": [
              "busy",
              "closed",
              "syncing"
            ],
            "type": "string",
            "x-nullable": true
          }
        },
        "required": [
          "detail"
        ],
        "type": "object"
      },
      "EvaluateParamsRequest": {
        "additionalProperties": false,
        "properties": {
          "ignore_revlogs_before_ms": {
            "type": "integer"
          },
          "params": {
            "items": {
              "type": "number"
            },
            "type": "array"
          },
          "search": {
            "default": "",
            "type": "string"
          }
        },
        "required": [
          "params"
        ],
        "type": "object"
      },
      "ExportRequest": {
        "additionalProperties": false,
        "description": "The deck to export, by name or id.",
        "properties": {
          "deck_id": {
            "type": "integer"
          },
          "deck_name": {
            "type": "string"
          },
          "path": {
            "description": "Destination .apkg path, on the Anki machine",
            "type": "string"
          },
          "with_media": {
            "default": true,
            "description": "Bundle referenced media files",
            "type": "boolean"
          },
          "with_scheduling": {
            "default": false,
            "description": "Include due dates and review history",
            "type": "boolean"
          }
        },
        "required": [
          "path"
        ],
        "type": "object"
      },
      "FieldCreate": {
        "additionalProperties": false,
        "description": "Schema for creating a field in a note type",
        "properties": {
          "collapsed": {
            "default": false,
            "type": "boolean"
          },
          "description": {
            "default": "",
            "type": "string"
          },
          "exclude_from_search": {
            "default": false,
            "type": "boolean"
          },
          "font": {
            "type": "string"
          },
          "index": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "plain_text": {
            "default": false,
            "type": "boolean"
          },
          "prevent_deletion": {
            "default": false,
            "type": "boolean"
          },
          "rtl": {
            "default": false,
            "type": "boolean"
          },
          "size": {
            "maximum": 65535.0,
            "minimum": 0.0,
            "type": "integer"
          },
          "sticky": {
            "default": false,
            "type": "boolean"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "FieldPatch": {
        "additionalProperties": false,
        "description": "Schema for patching a field - all fields optional",
        "properties": {
          "collapsed": {
            "type": "boolean"
          },
          "description": {
            "type": "string"
          },
          "exclude_from_search": {
            "type": "boolean"
          },
          "font": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "plain_text": {
            "type": "boolean"
          },
          "prevent_deletion": {
            "type": "boolean"
          },
          "rtl": {
            "type": "boolean"
          },
          "size": {
            "maximum": 65535.0,
            "minimum": 0.0,
            "type": "integer"
          },
          "sticky": {
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "FindReplaceRequest": {
        "additionalProperties": false,
        "description": "Literal (non-regex) replace across template sides and styling.",
        "properties": {
          "back": {
            "default": true,
            "type": "boolean"
          },
          "css": {
            "default": true,
            "type": "boolean"
          },
          "find": {
            "type": "string"
          },
          "front": {
            "default": true,
            "type": "boolean"
          },
          "note_type_id": {
            "type": "integer"
          },
          "note_type_name": {
            "type": "string"
          },
          "replace": {
            "type": "string"
          }
        },
        "required": [
          "find",
          "replace"
        ],
        "type": "object"
      },
      "FindReplaceResult": {
        "description": "`affected` counts note types that actually contained the text.",
        "properties": {
          "affected": {
            "type": "integer"
          },
          "stats": {
            "type": "object"
          }
        },
        "required": [
          "affected",
          "stats"
        ],
        "type": "object"
      },
      "ForgetRequest": {
        "additionalProperties": false,
        "description": "Base body for the batch scheduling verbs.",
        "properties": {
          "card_ids": {
            "description": "A card id sent twice counts once; a missing card is skipped.",
            "items": {
              "type": "integer"
            },
            "type": "array"
          },
          "reset_counts": {
            "default": false,
            "type": "boolean"
          },
          "restore_position": {
            "default": false,
            "type": "boolean"
          }
        },
        "required": [
          "card_ids"
        ],
        "type": "object"
      },
      "FsrsMemoryState": {
        "additionalProperties": false,
        "description": "FSRS's per-card memory model. Null until the card has been reviewed.",
        "properties": {
          "difficulty": {
            "description": "How hard the card is, from 1 (easiest) to 10 (hardest).",
            "type": "number"
          },
          "stability": {
            "description": "FSRS memory stability, in days.",
            "type": "number"
          }
        },
        "required": [
          "stability",
          "difficulty"
        ],
        "type": "object"
      },
      "Gap": {
        "additionalProperties": false,
        "description": "`gap`: this connection fell behind and messages were lost; reload what you show.",
        "properties": {
          "after_seq": {
            "type": "integer"
          },
          "discarded": {
            "description": "How many messages were lost.",
            "type": "integer"
          },
          "reason": {
            "enum": [
              "lagged"
            ],
            "type": "string"
          },
          "session_id": {
            "type": "string"
          },
          "ts": {
            "type": "integer"
          },
          "type": {
            "enum": [
              "gap"
            ],
            "type": "string"
          }
        },
        "required": [
          "type",
          "after_seq",
          "session_id",
          "ts",
          "reason",
          "discarded"
        ],
        "type": "object"
      },
      "GuiResult": {
        "description": "Did the UI do the thing.\n\nFalse rather than an error for the ordinary \"not right now\" cases - no\nBrowser open, no review in progress - because those are states a client\npolls for, not mistakes it made. Something named that doesn't exist is a 404.",
        "properties": {
          "ok": {
            "default": true,
            "type": "boolean"
          },
          "stats": {
            "type": "object"
          }
        },
        "type": "object"
      },
      "HTTPValidationError": {
        "properties": {
          "detail": {
            "type": "string"
          },
          "errors": {
            "items": {
              "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array"
          }
        },
        "required": [
          "detail",
          "errors"
        ],
        "type": "object"
      },
      "Health": {
        "properties": {
          "caller": {
            "allOf": [
              {
                "$ref": "#/components/schemas/CallerInfo"
              }
            ],
            "description": "Who this request counts as, and what became of its key (X-API-Key or a Bearer token). Needs no profile open"
          },
          "collection": {
            "$ref": "#/components/schemas/CollectionHealth"
          },
          "ok": {
            "description": "Whether the server is running",
            "type": "boolean"
          },
          "port": {
            "description": "Port number the server is listening on",
            "type": "integer"
          },
          "server": {
            "description": "Server name",
            "type": "string"
          },
          "version": {
            "description": "Legacy release version; use versions for explicit identifiers",
            "type": "string"
          },
          "versions": {
            "$ref": "#/components/schemas/Versions"
          }
        },
        "required": [
          "ok",
          "server",
          "version",
          "versions",
          "port",
          "collection",
          "caller"
        ],
        "type": "object"
      },
      "ImportFileRequest": {
        "additionalProperties": false,
        "properties": {
          "path": {
            "description": "File to import; omit to let the user pick one",
            "type": "string"
          }
        },
        "type": "object"
      },
      "ImportOptions": {
        "description": "Omitted or null options use Anki's saved import preferences.",
        "properties": {
          "merge_note_types": {
            "description": "Merge compatible note types. Omit or null: use Anki's saved choice.",
            "nullable": true,
            "type": "boolean"
          },
          "update_note_types": {
            "description": "When to update existing note types. Omit or null: use Anki's saved choice.",
            "enum": [
              "if_newer",
              "always",
              "never"
            ],
            "nullable": true,
            "type": "string"
          },
          "update_notes": {
            "description": "When to update existing notes. Omit or null: use Anki's saved choice.",
            "enum": [
              "if_newer",
              "always",
              "never"
            ],
            "nullable": true,
            "type": "string"
          },
          "with_deck_presets": {
            "description": "Import deck presets. Omit or null: use Anki's saved choice. Explicit values require backend support; inspect /v1/collection/import-options.",
            "nullable": true,
            "type": "boolean"
          },
          "with_scheduling": {
            "description": "Import due dates and review history. Omit or null: use Anki's saved choice.",
            "nullable": true,
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "ImportPreferences": {
        "properties": {
          "options": {
            "$ref": "#/components/schemas/ImportOptions"
          },
          "stats": {
            "type": "object"
          },
          "unsupported_options": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "options"
        ],
        "type": "object"
      },
      "ImportRequest": {
        "additionalProperties": false,
        "description": "Omitted or null options use Anki's saved import preferences.",
        "properties": {
          "merge_note_types": {
            "description": "Merge compatible note types. Omit or null: use Anki's saved choice.",
            "nullable": true,
            "type": "boolean"
          },
          "path": {
            "description": "Source .apkg path, on the Anki machine",
            "type": "string"
          },
          "update_note_types": {
            "description": "When to update existing note types. Omit or null: use Anki's saved choice.",
            "enum": [
              "if_newer",
              "always",
              "never"
            ],
            "nullable": true,
            "type": "string"
          },
          "update_notes": {
            "description": "When to update existing notes. Omit or null: use Anki's saved choice.",
            "enum": [
              "if_newer",
              "always",
              "never"
            ],
            "nullable": true,
            "type": "string"
          },
          "with_deck_presets": {
            "description": "Import deck presets. Omit or null: use Anki's saved choice. Explicit values require backend support; inspect /v1/collection/import-options.",
            "nullable": true,
            "type": "boolean"
          },
          "with_scheduling": {
            "description": "Import due dates and review history. Omit or null: use Anki's saved choice.",
            "nullable": true,
            "type": "boolean"
          }
        },
        "required": [
          "path"
        ],
        "type": "object"
      },
      "ImportResult": {
        "properties": {
          "imported": {
            "default": 0,
            "type": "integer"
          },
          "stats": {
            "type": "object"
          },
          "updated": {
            "default": 0,
            "type": "integer"
          }
        },
        "type": "object"
      },
      "InsertReviewsRequest": {
        "additionalProperties": false,
        "properties": {
          "reviews": {
            "items": {
              "$ref": "#/components/schemas/ReviewInfo"
            },
            "type": "array"
          }
        },
        "required": [
          "reviews"
        ],
        "type": "object"
      },
      "InsertReviewsResult": {
        "properties": {
          "inserted": {
            "type": "integer"
          },
          "stats": {
            "type": "object"
          }
        },
        "required": [
          "inserted",
          "stats"
        ],
        "type": "object"
      },
      "JobInfo": {
        "properties": {
          "error": {
            "description": "When failed or aborted: why; null otherwise",
            "type": "string",
            "x-nullable": true
          },
          "id": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "progress": {
            "allOf": [
              {
                "$ref": "#/components/schemas/JobProgress"
              }
            ],
            "description": "Best-effort progress, only while running; null otherwise",
            "x-nullable": true
          },
          "result": {
            "description": "When done: the result, shaped by the job's kind (see the route that started it); null before",
            "type": "object",
            "x-nullable": true
          },
          "stats": {
            "type": "object"
          },
          "status": {
            "enum": [
              "queued",
              "running",
              "done",
              "failed",
              "aborted"
            ],
            "type": "string"
          }
        },
        "required": [
          "id",
          "kind",
          "status",
          "stats"
        ],
        "type": "object"
      },
      "JobProgress": {
        "properties": {
          "current": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          }
        },
        "required": [
          "current",
          "total"
        ],
        "type": "object"
      },
      "JobSubmitted": {
        "properties": {
          "job_id": {
            "type": "string"
          },
          "stats": {
            "type": "object"
          },
          "status": {
            "enum": [
              "queued",
              "running",
              "done",
              "failed",
              "aborted"
            ],
            "type": "string"
          }
        },
        "required": [
          "job_id",
          "status",
          "stats"
        ],
        "type": "object"
      },
      "MatureToday": {
        "properties": {
          "correct": {
            "description": "Of those, answered Hard, Good or Easy.",
            "type": "integer"
          },
          "reviews": {
            "description": "Reviews of mature cards (interval of 21 days or more).",
            "type": "integer"
          }
        },
        "required": [
          "reviews",
          "correct"
        ],
        "type": "object"
      },
      "MediaCreateFailure": {
        "properties": {
          "code": {
            "description": "invalid_media: the file can't be stored as sent, such as a bad filename or base64 (see message). storage_error: Anki couldn't write it. source_error: its url or path couldn't be read.",
            "enum": [
              "invalid_media",
              "storage_error",
              "source_error"
            ],
            "type": "string"
          },
          "index": {
            "description": "Zero-based position in the submitted array; 0 for one object.",
            "type": "integer"
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "index",
          "code",
          "message"
        ],
        "type": "object"
      },
      "MediaCreateResponse": {
        "properties": {
          "created": {
            "items": {
              "$ref": "#/components/schemas/MediaCreated"
            },
            "type": "array"
          },
          "failed": {
            "items": {
              "$ref": "#/components/schemas/MediaCreateFailure"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "MediaCreated": {
        "properties": {
          "filename": {
            "type": "string"
          },
          "index": {
            "type": "integer"
          },
          "renamed": {
            "default": false,
            "type": "boolean"
          },
          "requested_filename": {
            "description": "The filename sent; null when none was.",
            "type": "string",
            "x-nullable": true
          },
          "size": {
            "type": "integer"
          }
        },
        "required": [
          "filename",
          "size",
          "index"
        ],
        "type": "object"
      },
      "MediaDeletionResult": {
        "properties": {
          "filename": {
            "type": "string"
          },
          "stats": {
            "type": "object"
          },
          "success": {
            "type": "boolean"
          }
        },
        "required": [
          "success",
          "filename",
          "stats"
        ],
        "type": "object"
      },
      "MediaFileRow": {
        "description": "A media file row. Without `select`, every field is present; with `select`, only those it names. With `shape=scalar`, each item is the selected field's bare value instead.",
        "properties": {
          "filename": {
            "description": "File name in the media folder.",
            "type": "string"
          },
          "modified": {
            "description": "Last modified, Unix seconds.",
            "type": "integer"
          },
          "size": {
            "description": "File size in bytes.",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "MediaStored": {
        "properties": {
          "filename": {
            "type": "string"
          },
          "renamed": {
            "default": false,
            "type": "boolean"
          },
          "requested_filename": {
            "description": "The filename sent; null when none was.",
            "type": "string",
            "x-nullable": true
          },
          "size": {
            "type": "integer"
          }
        },
        "required": [
          "filename",
          "size"
        ],
        "type": "object"
      },
      "MediaUpload": {
        "additionalProperties": false,
        "properties": {
          "data": {
            "type": "string"
          },
          "filename": {
            "type": "string"
          },
          "path": {
            "type": "string"
          },
          "url": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "MemoryStateEntry": {
        "additionalProperties": false,
        "description": "Per-card FSRS state write. An omitted field is left unchanged; an explicit\nnull clears it. The route preserves that distinction by handing the\nadapter dicts built with exclude_unset.",
        "properties": {
          "decay": {
            "type": "number"
          },
          "desired_retention": {
            "type": "number"
          },
          "id": {
            "type": "integer"
          },
          "memory_state": {
            "$ref": "#/components/schemas/FsrsMemoryState"
          }
        },
        "required": [
          "id"
        ],
        "type": "object"
      },
      "MutationResult_Any_": {
        "properties": {
          "result": {},
          "stats": {
            "type": "object"
          }
        },
        "required": [
          "stats"
        ],
        "type": "object"
      },
      "NoteAttachment": {
        "additionalProperties": false,
        "description": "A file stored with the note, as in AnkiConnect's addNote.",
        "properties": {
          "data": {
            "type": "string"
          },
          "fields": {
            "description": "Fields the file's reference is appended to; none stores it only.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "filename": {
            "type": "string"
          },
          "path": {
            "type": "string"
          },
          "url": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "NoteCheckResponse": {
        "properties": {
          "results": {
            "items": {
              "$ref": "#/components/schemas/NoteCheckResult"
            },
            "type": "array"
          },
          "stats": {
            "type": "object"
          }
        },
        "required": [
          "results",
          "stats"
        ],
        "type": "object"
      },
      "NoteCheckResult": {
        "properties": {
          "can_add": {
            "type": "boolean"
          },
          "duplicate_note_ids": {
            "description": "Matching note IDs, with include=duplicate_ids; null without it.",
            "items": {
              "type": "integer"
            },
            "type": "array",
            "x-nullable": true
          },
          "index": {
            "description": "Zero-based position in the request.",
            "type": "integer"
          },
          "reason": {
            "description": "Why it can't be added: the state, or what is wrong for invalid; null when it can.",
            "type": "string",
            "x-nullable": true
          },
          "state": {
            "description": "normal: it can be added. empty: its first field is empty. duplicate: it duplicates a note (it can still be added with allow_duplicate). missing_cloze: a cloze note without a cloze. invalid: the request names a deck, note type or field that doesn't exist (see reason). unknown: Anki gave a reason Tsunagi doesn't name.",
            "enum": [
              "normal",
              "empty",
              "duplicate",
              "missing_cloze",
              "invalid",
              "unknown"
            ],
            "type": "string"
          }
        },
        "required": [
          "index",
          "can_add",
          "state"
        ],
        "type": "object"
      },
      "NoteCreate": {
        "additionalProperties": false,
        "description": "A note as creation, checks and upsert take it; NoteCreate adds files.",
        "properties": {
          "allow_duplicate": {
            "default": false,
            "type": "boolean"
          },
          "audio": {
            "anyOf": [
              {
                "items": {
                  "$ref": "#/components/schemas/NoteAttachment"
                },
                "type": "array"
              },
              {
                "$ref": "#/components/schemas/NoteAttachment"
              }
            ]
          },
          "deck_id": {
            "type": "integer"
          },
          "deck_name": {
            "type": "string"
          },
          "duplicate_scope": {
            "description": "Where to look for duplicates: the whole collection (default) or one deck.",
            "enum": [
              "collection",
              "deck"
            ],
            "type": "string"
          },
          "duplicate_scope_options": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DuplicateScopeOptions"
              }
            ],
            "description": "Deck and note-type options for the duplicate check. When deck scope or check_all_note_types is used, notes match on the first field's checksum, as in AnkiConnect."
          },
          "fields": {
            "anyOf": [
              {
                "additionalProperties": {
                  "type": "string"
                },
                "type": "object"
              },
              {
                "items": {
                  "$ref": "#/components/schemas/NoteFieldValue"
                },
                "type": "array"
              }
            ]
          },
          "note_type_id": {
            "type": "integer"
          },
          "note_type_name": {
            "type": "string"
          },
          "picture": {
            "anyOf": [
              {
                "items": {
                  "$ref": "#/components/schemas/NoteAttachment"
                },
                "type": "array"
              },
              {
                "$ref": "#/components/schemas/NoteAttachment"
              }
            ]
          },
          "tags": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "video": {
            "anyOf": [
              {
                "items": {
                  "$ref": "#/components/schemas/NoteAttachment"
                },
                "type": "array"
              },
              {
                "$ref": "#/components/schemas/NoteAttachment"
              }
            ]
          }
        },
        "required": [
          "fields"
        ],
        "type": "object"
      },
      "NoteCreateFailure": {
        "properties": {
          "attachment": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AttachmentRef"
              }
            ],
            "description": "For code 'invalid_attachment': the file that failed.",
            "x-nullable": true
          },
          "code": {
            "description": "duplicate: it duplicates a note (see duplicate_note_ids). invalid_note: it can't be made as sent, such as a deck, note type or field that doesn't exist (see message). invalid_attachment: one of its files (see attachment). anki_error: Anki refused it (see message).",
            "enum": [
              "duplicate",
              "invalid_note",
              "invalid_attachment",
              "anki_error"
            ],
            "type": "string"
          },
          "duplicate_note_ids": {
            "description": "For code 'duplicate', with include=duplicate_ids: the existing notes it duplicates.",
            "items": {
              "type": "integer"
            },
            "type": "array",
            "x-nullable": true
          },
          "index": {
            "description": "Zero-based position in the submitted array; 0 for one object.",
            "type": "integer"
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "index",
          "code",
          "message"
        ],
        "type": "object"
      },
      "NoteCreateResponse": {
        "properties": {
          "created": {
            "items": {
              "$ref": "#/components/schemas/NoteCreated"
            },
            "type": "array"
          },
          "failed": {
            "items": {
              "$ref": "#/components/schemas/NoteCreateFailure"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "NoteCreated": {
        "properties": {
          "cards": {
            "description": "The note's card IDs, with include=cards; null without it.",
            "items": {
              "type": "integer"
            },
            "type": "array",
            "x-nullable": true
          },
          "files": {
            "description": "The note's audio, video and picture files as stored, in that order, as POST /v1/media reports them. `filename` differs from `requested_filename` when Anki renamed the file; references in the fields a file lists follow the rename, others don't. Present when the note had files; null when it had none.",
            "items": {
              "$ref": "#/components/schemas/MediaStored"
            },
            "type": "array",
            "x-nullable": true
          },
          "id": {
            "type": "integer"
          },
          "index": {
            "description": "Zero-based position in the submitted array; 0 for one object.",
            "type": "integer"
          }
        },
        "required": [
          "index",
          "id"
        ],
        "type": "object"
      },
      "NoteField": {
        "description": "One field of a note. Deliberately mirrors NoteTypeField's shape so the query\nDSL reads the same on both resources: where=fields[].name==Front,\nselect=fields[].(name,value).",
        "properties": {
          "index": {
            "description": "Field position in the note type, from 0.",
            "type": "integer"
          },
          "name": {
            "description": "Field name.",
            "type": "string"
          },
          "value": {
            "description": "Field content, as stored (HTML).",
            "type": "string"
          }
        },
        "required": [
          "name",
          "value",
          "index"
        ],
        "type": "object"
      },
      "NoteFieldValue": {
        "additionalProperties": false,
        "description": "Array form of a field on write.",
        "properties": {
          "index": {
            "description": "Accepted and ignored: a field's position comes from its note type",
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "value": {
            "default": "",
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "NoteIdList": {
        "properties": {
          "note_ids": {
            "items": {
              "type": "integer"
            },
            "type": "array"
          },
          "stats": {
            "type": "object"
          }
        },
        "type": "object"
      },
      "NoteIdRequest": {
        "additionalProperties": false,
        "properties": {
          "note_id": {
            "type": "integer"
          }
        },
        "required": [
          "note_id"
        ],
        "type": "object"
      },
      "NoteIds": {
        "additionalProperties": false,
        "description": "Body for the batch note verbs, as CardIds is for cards.",
        "properties": {
          "note_ids": {
            "description": "A note id sent twice counts once; a missing note is skipped.",
            "items": {
              "type": "integer"
            },
            "type": "array"
          }
        },
        "required": [
          "note_ids"
        ],
        "type": "object"
      },
      "NotePatch": {
        "additionalProperties": false,
        "description": "Files sent with a note, on creation and on PATCH.",
        "properties": {
          "add_tags": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "audio": {
            "anyOf": [
              {
                "items": {
                  "$ref": "#/components/schemas/NoteAttachment"
                },
                "type": "array"
              },
              {
                "$ref": "#/components/schemas/NoteAttachment"
              }
            ]
          },
          "fields": {
            "anyOf": [
              {
                "additionalProperties": {
                  "type": "string"
                },
                "type": "object"
              },
              {
                "items": {
                  "$ref": "#/components/schemas/NoteFieldValue"
                },
                "type": "array"
              }
            ]
          },
          "note_type_id": {
            "description": "Deprecated, removed in 0.8: use POST /v1/notes:change-note-type. Changes the note's type; needs `fields`, as the new type's fields start empty.",
            "type": "integer"
          },
          "note_type_name": {
            "description": "Deprecated, removed in 0.8: use POST /v1/notes:change-note-type.",
            "type": "string"
          },
          "picture": {
            "anyOf": [
              {
                "items": {
                  "$ref": "#/components/schemas/NoteAttachment"
                },
                "type": "array"
              },
              {
                "$ref": "#/components/schemas/NoteAttachment"
              }
            ]
          },
          "remove_tags": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "tags": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "video": {
            "anyOf": [
              {
                "items": {
                  "$ref": "#/components/schemas/NoteAttachment"
                },
                "type": "array"
              },
              {
                "$ref": "#/components/schemas/NoteAttachment"
              }
            ]
          }
        },
        "type": "object"
      },
      "NoteRow": {
        "description": "A note row. Without `select`, every field is present; with `select`, only those it names. With `shape=scalar`, each item is the selected field's bare value instead.",
        "properties": {
          "cards": {
            "description": "Ids of the note's cards, in template order.",
            "items": {
              "type": "integer"
            },
            "type": "array"
          },
          "fields": {
            "description": "The note's fields, in note type order.",
            "items": {
              "$ref": "#/components/schemas/NoteField"
            },
            "type": "array",
            "x-from": [
              "note_types"
            ]
          },
          "first_field": {
            "description": "The first field's content, as stored (HTML).",
            "type": "string"
          },
          "guid": {
            "description": "Globally unique id Anki uses to match the note across syncs and imports.",
            "type": "string"
          },
          "id": {
            "description": "Note id: its creation time in epoch milliseconds.",
            "type": "integer"
          },
          "modified": {
            "description": "Last modified, Unix seconds.",
            "type": "integer"
          },
          "note_type_id": {
            "description": "Id of the note's note type.",
            "type": "integer"
          },
          "note_type_name": {
            "description": "Name of the note's note type.",
            "type": "string",
            "x-from": [
              "note_types"
            ]
          },
          "tags": {
            "description": "The note's tags.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "usn": {
            "description": "Update sequence number for syncing; -1 means changed since the last sync.",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "NoteTypeChange": {
        "additionalProperties": false,
        "description": "Body for POST /v1/notes:change-note-type (6.118): the new note type by name or id\n(the id when both are sent), and which old field and card template each new one takes.",
        "properties": {
          "dry_run": {
            "default": false,
            "description": "Answer what the change would do, and change nothing.",
            "type": "boolean"
          },
          "fields": {
            "additionalProperties": {
              "type": "string"
            },
            "description": "For a new field, the old field whose content it takes, or null for none. A field not named takes Anki's default, as its Change Note Type window proposes: the old field of the same name, else the one in the same position.",
            "type": "object"
          },
          "note_ids": {
            "description": "The notes to change, all of one note type. A note id sent twice counts once; a missing note is skipped.",
            "items": {
              "type": "integer"
            },
            "type": "array"
          },
          "note_type_id": {
            "type": "integer"
          },
          "note_type_name": {
            "type": "string"
          },
          "templates": {
            "additionalProperties": {
              "type": "string"
            },
            "description": "For a new card template, the old template whose cards (and their reviews) it takes, or null for none; Anki's default otherwise. Cards of an old template that no new one takes are deleted. Not with a cloze note type on either side: cloze cards follow their cloze numbers.",
            "type": "object"
          }
        },
        "required": [
          "note_ids"
        ],
        "type": "object"
      },
      "NoteTypeChangeResult": {
        "properties": {
          "affected": {
            "description": "The notes changed (with dry_run, that would be).",
            "type": "integer"
          },
          "cards_removed": {
            "description": "Cards deleted because no new template takes them.",
            "type": "integer"
          },
          "dry_run": {
            "type": "boolean"
          },
          "fields": {
            "additionalProperties": {
              "type": "string"
            },
            "description": "Each new field, and the old field whose content it takes (null: none).",
            "type": "object"
          },
          "lost_fields": {
            "description": "Old fields whose content no new field takes.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "stats": {
            "type": "object"
          },
          "templates": {
            "additionalProperties": {
              "type": "string"
            },
            "description": "Each new card template, and the old template whose cards it takes (null: none); null when either note type is cloze.",
            "type": "object",
            "x-nullable": true
          }
        },
        "required": [
          "affected",
          "fields",
          "lost_fields",
          "cards_removed",
          "dry_run",
          "stats"
        ],
        "type": "object"
      },
      "NoteTypeCreate": {
        "additionalProperties": false,
        "description": "Schema for creating a note type",
        "properties": {
          "css": {
            "type": "string"
          },
          "fields": {
            "items": {
              "$ref": "#/components/schemas/FieldCreate"
            },
            "type": "array"
          },
          "name": {
            "type": "string"
          },
          "sort_field": {
            "default": 0,
            "type": "integer"
          },
          "templates": {
            "items": {
              "$ref": "#/components/schemas/TemplateCreate"
            },
            "type": "array"
          },
          "type": {
            "type": "integer"
          }
        },
        "required": [
          "name",
          "fields",
          "templates"
        ],
        "type": "object"
      },
      "NoteTypeField": {
        "properties": {
          "collapsed": {
            "default": false,
            "description": "Collapse the field in the editor by default.",
            "type": "boolean"
          },
          "description": {
            "default": "",
            "description": "Text shown inside the field in the editor while it is empty.",
            "type": "string"
          },
          "exclude_from_search": {
            "default": false,
            "description": "Leave the field out of searches that don't name a field.",
            "type": "boolean"
          },
          "font": {
            "description": "Font used for the field in the editor.",
            "type": "string"
          },
          "id": {
            "description": "Stable field id Anki uses to merge note types on import; null on fields from older versions.",
            "type": "integer",
            "x-nullable": true
          },
          "index": {
            "description": "Field position, from 0.",
            "type": "integer"
          },
          "name": {
            "description": "Field name.",
            "type": "string"
          },
          "plain_text": {
            "default": false,
            "description": "Open the field in the HTML editor by default.",
            "type": "boolean"
          },
          "prevent_deletion": {
            "default": false,
            "description": "The field can't be deleted (required by the note type, e.g. Image Occlusion).",
            "type": "boolean"
          },
          "rtl": {
            "default": false,
            "description": "Edit the field right to left.",
            "type": "boolean"
          },
          "size": {
            "description": "Font size used for the field in the editor.",
            "type": "integer"
          },
          "sticky": {
            "default": false,
            "description": "Keep the field's last value when adding the next note.",
            "type": "boolean"
          }
        },
        "required": [
          "name",
          "index"
        ],
        "type": "object"
      },
      "NoteTypePatch": {
        "additionalProperties": false,
        "description": "Schema for patching a note type - all fields optional",
        "properties": {
          "css": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "sort_field": {
            "type": "integer"
          },
          "type": {
            "type": "integer"
          }
        },
        "type": "object"
      },
      "NoteTypeRow": {
        "description": "A note type row. Without `select`, every field is present; with `select`, only those it names. With `shape=scalar`, each item is the selected field's bare value instead.",
        "properties": {
          "css": {
            "description": "Styling shared by all the note type's cards.",
            "type": "string"
          },
          "fields": {
            "description": "The note type's fields, in order.",
            "items": {
              "$ref": "#/components/schemas/NoteTypeField"
            },
            "type": "array"
          },
          "id": {
            "description": "Note type id.",
            "type": "integer"
          },
          "latex_post": {
            "description": "LaTeX footer added after each LaTeX snippet.",
            "type": "string"
          },
          "latex_pre": {
            "description": "LaTeX header added before each LaTeX snippet.",
            "type": "string"
          },
          "latex_svg": {
            "description": "Whether LaTeX is rendered as SVG instead of PNG.",
            "type": "boolean"
          },
          "modified": {
            "description": "Last modified, Unix seconds.",
            "type": "integer"
          },
          "name": {
            "description": "Note type name.",
            "type": "string"
          },
          "note_count": {
            "description": "How many notes use this note type. Computed by native note type queries; may be null in mutation replies.",
            "type": "integer"
          },
          "original_id": {
            "description": "For an imported note type, its id in the collection it came from; null otherwise.",
            "type": "integer",
            "x-nullable": true
          },
          "original_stock_kind": {
            "description": "The built-in type this note type was made from; null when unknown or before Anki 2.1.62.",
            "type": "integer",
            "x-nullable": true,
            "x-values": {
              "1": "basic",
              "2": "basic_and_reversed",
              "3": "basic_optional_reversed",
              "4": "basic_typing",
              "5": "cloze",
              "6": "image_occlusion"
            }
          },
          "req": {
            "description": "Fields each template needs: [template ord, \"any\"|\"all\"|\"none\", [field ords]].",
            "items": {
              "items": [
                {
                  "type": "integer"
                },
                {
                  "type": "string"
                },
                {
                  "items": {
                    "type": "integer"
                  },
                  "type": "array"
                }
              ],
              "maxItems": 3,
              "minItems": 3,
              "type": "array"
            },
            "type": "array"
          },
          "sort_field": {
            "description": "Position (from 0) of the field the browser shows and sorts by.",
            "type": "integer"
          },
          "templates": {
            "description": "The note type's card templates, in order.",
            "items": {
              "$ref": "#/components/schemas/CardTemplate"
            },
            "type": "array"
          },
          "type": {
            "description": "Standard or cloze.",
            "type": "integer",
            "x-values": {
              "0": "standard",
              "1": "cloze"
            }
          },
          "usn": {
            "description": "Update sequence number for syncing; -1 means changed since the last sync.",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "NoteUpdated": {
        "properties": {
          "cards": {
            "description": "The note's card IDs, with include=cards; null without it.",
            "items": {
              "type": "integer"
            },
            "type": "array",
            "x-nullable": true
          },
          "fields_changed": {
            "description": "Fields whose value changed; empty when nothing did.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "id": {
            "type": "integer"
          },
          "index": {
            "description": "Zero-based position in the submitted array; 0 for one object.",
            "type": "integer"
          },
          "tags_changed": {
            "type": "boolean"
          }
        },
        "required": [
          "index",
          "id",
          "fields_changed",
          "tags_changed"
        ],
        "type": "object"
      },
      "NoteUpsert": {
        "additionalProperties": false,
        "description": "A note as creation, checks and upsert take it; NoteCreate adds files.",
        "properties": {
          "allow_duplicate": {
            "default": false,
            "type": "boolean"
          },
          "deck_id": {
            "type": "integer"
          },
          "deck_name": {
            "type": "string"
          },
          "duplicate_scope": {
            "description": "Where to look for duplicates: the whole collection (default) or one deck.",
            "enum": [
              "collection",
              "deck"
            ],
            "type": "string"
          },
          "duplicate_scope_options": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DuplicateScopeOptions"
              }
            ],
            "description": "Deck and note-type options for the duplicate check. When deck scope or check_all_note_types is used, notes match on the first field's checksum, as in AnkiConnect."
          },
          "fields": {
            "anyOf": [
              {
                "additionalProperties": {
                  "type": "string"
                },
                "type": "object"
              },
              {
                "items": {
                  "$ref": "#/components/schemas/NoteFieldValue"
                },
                "type": "array"
              }
            ]
          },
          "match": {
            "$ref": "#/components/schemas/UpsertMatch"
          },
          "note_type_id": {
            "type": "integer"
          },
          "note_type_name": {
            "type": "string"
          },
          "on_match": {
            "$ref": "#/components/schemas/OnMatch"
          },
          "tags": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "fields"
        ],
        "type": "object"
      },
      "NoteUpsertFailure": {
        "properties": {
          "code": {
            "description": "duplicate: no note matched and the new one would duplicate one. invalid_note: it can't be made as sent, such as a field that doesn't exist (see message). ambiguous: more than one note matched. anki_error: Anki refused it (see message).",
            "enum": [
              "duplicate",
              "invalid_note",
              "ambiguous",
              "anki_error"
            ],
            "type": "string"
          },
          "index": {
            "description": "Zero-based position in the submitted array; 0 for one object.",
            "type": "integer"
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "index",
          "code",
          "message"
        ],
        "type": "object"
      },
      "NoteUpsertResponse": {
        "properties": {
          "created": {
            "items": {
              "$ref": "#/components/schemas/NoteCreated"
            },
            "type": "array"
          },
          "failed": {
            "items": {
              "$ref": "#/components/schemas/NoteUpsertFailure"
            },
            "type": "array"
          },
          "updated": {
            "items": {
              "$ref": "#/components/schemas/NoteUpdated"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "OnMatch": {
        "additionalProperties": false,
        "properties": {
          "fields": {
            "additionalProperties": {
              "enum": [
                "keep",
                "replace",
                "replace_if_empty",
                "append"
              ],
              "type": "string"
            },
            "description": "Per field: keep, replace (unless the new value is empty), replace_if_empty, or append (after `separator`, skipped when the value is already there). \"*\" sets the rule for the others; default replace_if_empty. Fields the request does not send are never touched.",
            "type": "object"
          },
          "separator": {
            "default": "<br>",
            "description": "Put between the old and new value by append.",
            "type": "string"
          },
          "tags": {
            "default": "union",
            "description": "union adds the request's tags; replace sets them; keep leaves them.",
            "enum": [
              "union",
              "replace",
              "keep"
            ],
            "type": "string"
          }
        },
        "type": "object"
      },
      "OperationCapability": {
        "properties": {
          "operation_id": {
            "type": "string"
          },
          "options": {
            "additionalProperties": {
              "$ref": "#/components/schemas/CapabilityState"
            },
            "description": "Options with settings or version restrictions; other inputs follow the operation schema",
            "type": "object"
          },
          "reason": {
            "description": "Why it is disabled or unsupported, or what limits it; null when nothing does",
            "type": "string",
            "x-nullable": true
          },
          "setting": {
            "description": "Setting controlling this capability; null when none does",
            "type": "string",
            "x-nullable": true
          },
          "status": {
            "default": "available",
            "description": "Effective support after checking the running Anki and settings; request validation still applies",
            "enum": [
              "available",
              "disabled",
              "unsupported"
            ],
            "type": "string"
          }
        },
        "required": [
          "operation_id"
        ],
        "type": "object"
      },
      "OptimalRetentionResult": {
        "properties": {
          "retention": {
            "type": "number"
          },
          "stats": {
            "type": "object"
          }
        },
        "required": [
          "retention",
          "stats"
        ],
        "type": "object"
      },
      "Paginated_CardRow_": {
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/CardRow"
            },
            "type": "array"
          },
          "next_cursor": {
            "type": "string"
          },
          "stats": {
            "type": "object"
          },
          "total": {
            "description": "With include=total: every row the query matches, across all pages",
            "type": "integer"
          }
        },
        "required": [
          "items",
          "stats"
        ],
        "type": "object"
      },
      "Paginated_DeckPresetRow_": {
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/DeckPresetRow"
            },
            "type": "array"
          },
          "next_cursor": {
            "type": "string"
          },
          "stats": {
            "type": "object"
          },
          "total": {
            "description": "With include=total: every row the query matches, across all pages",
            "type": "integer"
          }
        },
        "required": [
          "items",
          "stats"
        ],
        "type": "object"
      },
      "Paginated_DeckRow_": {
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/DeckRow"
            },
            "type": "array"
          },
          "next_cursor": {
            "type": "string"
          },
          "stats": {
            "type": "object"
          },
          "total": {
            "description": "With include=total: every row the query matches, across all pages",
            "type": "integer"
          }
        },
        "required": [
          "items",
          "stats"
        ],
        "type": "object"
      },
      "Paginated_MediaFileRow_": {
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/MediaFileRow"
            },
            "type": "array"
          },
          "next_cursor": {
            "type": "string"
          },
          "stats": {
            "type": "object"
          },
          "total": {
            "description": "With include=total: every row the query matches, across all pages",
            "type": "integer"
          }
        },
        "required": [
          "items",
          "stats"
        ],
        "type": "object"
      },
      "Paginated_NoteRow_": {
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/NoteRow"
            },
            "type": "array"
          },
          "next_cursor": {
            "type": "string"
          },
          "stats": {
            "type": "object"
          },
          "total": {
            "description": "With include=total: every row the query matches, across all pages",
            "type": "integer"
          }
        },
        "required": [
          "items",
          "stats"
        ],
        "type": "object"
      },
      "Paginated_NoteTypeRow_": {
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/NoteTypeRow"
            },
            "type": "array"
          },
          "next_cursor": {
            "type": "string"
          },
          "stats": {
            "type": "object"
          },
          "total": {
            "description": "With include=total: every row the query matches, across all pages",
            "type": "integer"
          }
        },
        "required": [
          "items",
          "stats"
        ],
        "type": "object"
      },
      "Paginated_ReviewDayRow_": {
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/ReviewDayRow"
            },
            "type": "array"
          },
          "next_cursor": {
            "type": "string"
          },
          "stats": {
            "type": "object"
          },
          "total": {
            "description": "With include=total: every row the query matches, across all pages",
            "type": "integer"
          }
        },
        "required": [
          "items",
          "stats"
        ],
        "type": "object"
      },
      "Paginated_ReviewRow_": {
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/ReviewRow"
            },
            "type": "array"
          },
          "next_cursor": {
            "type": "string"
          },
          "stats": {
            "type": "object"
          },
          "total": {
            "description": "With include=total: every row the query matches, across all pages",
            "type": "integer"
          }
        },
        "required": [
          "items",
          "stats"
        ],
        "type": "object"
      },
      "Paginated_TagRow_": {
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/TagRow"
            },
            "type": "array"
          },
          "next_cursor": {
            "type": "string"
          },
          "stats": {
            "type": "object"
          },
          "total": {
            "description": "With include=total: every row the query matches, across all pages",
            "type": "integer"
          }
        },
        "required": [
          "items",
          "stats"
        ],
        "type": "object"
      },
      "PermissionRequestResult": {
        "description": "The answer to an access request (6.26, 6.124).",
        "properties": {
          "app": {
            "description": "Its app's name in Apps & keys. Null when denied.",
            "type": "string",
            "x-nullable": true
          },
          "key": {
            "description": "Its app's key, sent this once: keep it and send it as X-Api-Key. Null when denied.",
            "type": "string",
            "x-nullable": true
          },
          "key_required": {
            "description": "Whether a request without a key has No access here (the \"No key, this computer\" row).",
            "type": "boolean"
          },
          "permission": {
            "description": "granted: you allowed it; denied: you didn't, it's on a list to ignore, or no one answered in two minutes.",
            "enum": [
              "granted",
              "denied"
            ],
            "type": "string"
          },
          "role": {
            "description": "The role you gave it, by name. Null when denied.",
            "type": "string",
            "x-nullable": true
          }
        },
        "required": [
          "permission",
          "key_required"
        ],
        "type": "object"
      },
      "ProfileList": {
        "description": "Every profile Anki knows about, and which one is open.",
        "properties": {
          "active": {
            "type": "string"
          },
          "items": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "stats": {
            "type": "object"
          }
        },
        "required": [
          "items"
        ],
        "type": "object"
      },
      "ProfileLoad": {
        "additionalProperties": false,
        "properties": {
          "name": {
            "description": "Profile to switch to",
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "ProfileLoadResult": {
        "properties": {
          "loaded": {
            "type": "boolean"
          },
          "stats": {
            "type": "object"
          }
        },
        "required": [
          "loaded"
        ],
        "type": "object"
      },
      "QueryRequest": {
        "additionalProperties": false,
        "properties": {
          "cursor": {
            "type": "string"
          },
          "distinct_on": {
            "type": "string"
          },
          "include": {
            "type": "string"
          },
          "limit": {
            "description": "Maximum results in this response; 0 for none (with include=total, the count alone). Omit to return all matches; no fixed upper cap.",
            "minimum": 0.0,
            "type": "integer"
          },
          "order": {
            "type": "string"
          },
          "search": {
            "type": "string"
          },
          "select": {
            "type": "string"
          },
          "shape": {
            "default": "object",
            "enum": [
              "object",
              "scalar"
            ],
            "type": "string"
          },
          "where": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "Ready": {
        "additionalProperties": false,
        "description": "`ready`: connected; load the data you show now.",
        "properties": {
          "after_seq": {
            "description": "Messages after this number come on this connection.",
            "type": "integer"
          },
          "heartbeat_ms": {
            "description": "How often a heartbeat comment is sent; a few missed means a dead connection.",
            "type": "integer"
          },
          "resources": {
            "description": "The resources this stream carries: those asked for that the app may read.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "session_id": {
            "type": "string"
          },
          "ts": {
            "type": "integer"
          },
          "type": {
            "enum": [
              "ready"
            ],
            "type": "string"
          }
        },
        "required": [
          "type",
          "session_id",
          "after_seq",
          "ts",
          "resources",
          "heartbeat_ms"
        ],
        "type": "object"
      },
      "RepositionRequest": {
        "additionalProperties": false,
        "description": "Base body for the batch scheduling verbs.",
        "properties": {
          "card_ids": {
            "description": "A card id sent twice counts once; a missing card is skipped.",
            "items": {
              "type": "integer"
            },
            "type": "array"
          },
          "randomize": {
            "default": false,
            "type": "boolean"
          },
          "shift_existing": {
            "default": false,
            "type": "boolean"
          },
          "starting_from": {
            "default": 0,
            "type": "integer"
          },
          "step_size": {
            "default": 1,
            "type": "integer"
          }
        },
        "required": [
          "card_ids"
        ],
        "type": "object"
      },
      "ReviewDayRow": {
        "description": "A review day row. Without `select`, every field is present; with `select`, only those it names. With `shape=scalar`, each item is the selected field's bare value instead.",
        "properties": {
          "date": {
            "description": "The Anki day, as the date it started (days start at the \"Next day starts at\" hour).",
            "type": "string"
          },
          "duration_ms": {
            "description": "Time spent answering, in milliseconds.",
            "type": "integer"
          },
          "filtered": {
            "description": "Answers in filtered decks, ahead of schedule.",
            "type": "integer"
          },
          "learn": {
            "description": "Answers to cards in learning.",
            "type": "integer"
          },
          "mature": {
            "description": "Answers to review cards with an interval of 21 days or more.",
            "type": "integer"
          },
          "relearn": {
            "description": "Answers to cards in relearning.",
            "type": "integer"
          },
          "reviews": {
            "type": "integer"
          },
          "young": {
            "description": "Answers to review cards with an interval under 21 days.",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "ReviewInfo": {
        "additionalProperties": false,
        "description": "One revlog row, with human-readable names (Anki wire names as aliases).\nNot every row is an answer: manual reschedules (type 4) and FSRS\nreschedules (type 5) are rows too, with rating 0.\n\nThe revlog is append-only history: every row is a fact about a review that\nhappened, so it is normally the scheduler's to write. The one exception is\nPOST /v1/reviews (AnkiConnect's insertReviews), which inserts rows for\nhistory imports - and reuses this model, so rows round-trip read<->write.",
        "properties": {
          "card_id": {
            "description": "Id of the card reviewed.",
            "type": "integer"
          },
          "duration_ms": {
            "default": 0,
            "description": "Time spent answering, milliseconds; 0 on reschedule rows.",
            "type": "integer"
          },
          "ease_factor": {
            "default": 0,
            "description": "SM-2: ease after the review, permille (2500 = 250%). FSRS: difficulty mapped to 100-1100. 0 if neither.",
            "type": "integer"
          },
          "id": {
            "description": "When the review happened, epoch milliseconds; also the row's id.",
            "type": "integer"
          },
          "interval": {
            "default": 0,
            "description": "Interval after the review: days if positive, seconds if negative (learning).",
            "type": "integer"
          },
          "last_interval": {
            "default": 0,
            "description": "Interval before the review, in the same units as interval.",
            "type": "integer"
          },
          "rating": {
            "default": 0,
            "description": "Answer button pressed: again, hard, good or easy (on old v1 learning rows, 2 was Good and 3 Easy). none (0) when the row records a reschedule rather than an answer: a set due date, a reset, or an FSRS reschedule (type manual or rescheduled).",
            "type": "integer",
            "x-values": {
              "0": "none",
              "1": "again",
              "2": "hard",
              "3": "good",
              "4": "easy"
            }
          },
          "type": {
            "default": 0,
            "description": "What kind of entry the row is: learning, review, relearning, filtered (an early review or cram in a filtered deck; cram rows have ease_factor 0), manual (a set due date or a reset; reset rows have ease_factor 0), or rescheduled (by FSRS when deck options changed).",
            "type": "integer",
            "x-values": {
              "0": "learning",
              "1": "review",
              "2": "relearning",
              "3": "filtered",
              "4": "manual",
              "5": "rescheduled"
            }
          },
          "usn": {
            "default": 0,
            "description": "Update sequence number for syncing; -1 means not yet synced.",
            "type": "integer"
          }
        },
        "required": [
          "id",
          "card_id"
        ],
        "type": "object"
      },
      "ReviewRow": {
        "description": "A review row. Without `select`, every field is present; with `select`, only those it names. With `shape=scalar`, each item is the selected field's bare value instead.",
        "properties": {
          "card_id": {
            "description": "Id of the card reviewed.",
            "type": "integer"
          },
          "duration_ms": {
            "description": "Time spent answering, milliseconds; 0 on reschedule rows.",
            "type": "integer"
          },
          "ease_factor": {
            "description": "SM-2: ease after the review, permille (2500 = 250%). FSRS: difficulty mapped to 100-1100. 0 if neither.",
            "type": "integer"
          },
          "id": {
            "description": "When the review happened, epoch milliseconds; also the row's id.",
            "type": "integer"
          },
          "interval": {
            "description": "Interval after the review: days if positive, seconds if negative (learning).",
            "type": "integer"
          },
          "last_interval": {
            "description": "Interval before the review, in the same units as interval.",
            "type": "integer"
          },
          "rating": {
            "description": "Answer button pressed: again, hard, good or easy (on old v1 learning rows, 2 was Good and 3 Easy). none (0) when the row records a reschedule rather than an answer: a set due date, a reset, or an FSRS reschedule (type manual or rescheduled).",
            "type": "integer",
            "x-values": {
              "0": "none",
              "1": "again",
              "2": "hard",
              "3": "good",
              "4": "easy"
            }
          },
          "type": {
            "description": "What kind of entry the row is: learning, review, relearning, filtered (an early review or cram in a filtered deck; cram rows have ease_factor 0), manual (a set due date or a reset; reset rows have ease_factor 0), or rescheduled (by FSRS when deck options changed).",
            "type": "integer",
            "x-values": {
              "0": "learning",
              "1": "review",
              "2": "relearning",
              "3": "filtered",
              "4": "manual",
              "5": "rescheduled"
            }
          },
          "usn": {
            "description": "Update sequence number for syncing; -1 means not yet synced.",
            "type": "integer"
          }
        },
        "type": "object"
      },
      "RowsChanged": {
        "additionalProperties": false,
        "description": "`<resource>.created`, `.updated`, `.deleted`; `reviews.created`.",
        "properties": {
          "anki": {
            "description": "Anki's own description of the change, for debugging; don't rely on it",
            "type": "object",
            "x-nullable": true
          },
          "app": {
            "description": "For by api: the app that made it; null otherwise",
            "type": "string",
            "x-nullable": true
          },
          "by": {
            "description": "api: made through either API; ui: made in Anki; null when unknown",
            "enum": [
              "ui",
              "api"
            ],
            "type": "string",
            "x-nullable": true
          },
          "ids": {
            "description": "The ids of the rows that changed.",
            "items": {
              "type": "integer"
            },
            "type": "array"
          },
          "seq": {
            "description": "Its number in the session; the SSE `id:` is `<session_id>:<seq>`.",
            "type": "integer"
          },
          "session_id": {
            "description": "The server session; a new one after a restart or profile switch.",
            "type": "string"
          },
          "ts": {
            "description": "When it was sent, Unix milliseconds.",
            "type": "integer"
          },
          "type": {
            "description": "The message's name, also its SSE `event:`.",
            "type": "string"
          }
        },
        "required": [
          "type",
          "seq",
          "session_id",
          "ts",
          "ids"
        ],
        "type": "object"
      },
      "RowsStale": {
        "additionalProperties": false,
        "description": "`<resource>.stale`: rows changed, but which isn't known; reload what you show.",
        "properties": {
          "anki": {
            "description": "Anki's own description of the change, for debugging; don't rely on it",
            "type": "object",
            "x-nullable": true
          },
          "app": {
            "description": "For by api: the app that made it; null otherwise",
            "type": "string",
            "x-nullable": true
          },
          "by": {
            "description": "api: made through either API; ui: made in Anki; null when unknown",
            "enum": [
              "ui",
              "api"
            ],
            "type": "string",
            "x-nullable": true
          },
          "reason": {
            "description": "collection: anything may have changed (a sync, a full reset); details_unavailable: Anki didn't say which rows (an undo, more than 1,000 ids)",
            "enum": [
              "collection",
              "details_unavailable"
            ],
            "type": "string"
          },
          "seq": {
            "description": "Its number in the session; the SSE `id:` is `<session_id>:<seq>`.",
            "type": "integer"
          },
          "session_id": {
            "description": "The server session; a new one after a restart or profile switch.",
            "type": "string"
          },
          "ts": {
            "description": "When it was sent, Unix milliseconds.",
            "type": "integer"
          },
          "type": {
            "description": "The message's name, also its SSE `event:`.",
            "type": "string"
          }
        },
        "required": [
          "type",
          "seq",
          "session_id",
          "ts",
          "reason"
        ],
        "type": "object"
      },
      "SetAddNoteDataRequest": {
        "additionalProperties": false,
        "description": "A deck and a note type each by name or id (the id when both are sent), as note writes take them.",
        "properties": {
          "append": {
            "default": false,
            "description": "Append to existing field values and tags instead of replacing",
            "type": "boolean"
          },
          "deck_id": {
            "type": "integer"
          },
          "deck_name": {
            "type": "string"
          },
          "fields": {
            "additionalProperties": {
              "type": "string"
            },
            "type": "object"
          },
          "note_type_id": {
            "type": "integer"
          },
          "note_type_name": {
            "type": "string"
          },
          "tags": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "SetCardValuesRequest": {
        "additionalProperties": false,
        "properties": {
          "card_id": {
            "type": "integer"
          },
          "force": {
            "default": false,
            "type": "boolean"
          },
          "values": {
            "type": "object"
          }
        },
        "required": [
          "card_id",
          "values"
        ],
        "type": "object"
      },
      "SetDueDateRequest": {
        "additionalProperties": false,
        "description": "Base body for the batch scheduling verbs.",
        "properties": {
          "card_ids": {
            "description": "A card id sent twice counts once; a missing card is skipped.",
            "items": {
              "type": "integer"
            },
            "type": "array"
          },
          "config_key": {
            "type": "string"
          },
          "days": {
            "type": "string"
          }
        },
        "required": [
          "card_ids",
          "days"
        ],
        "type": "object"
      },
      "SetEaseRequest": {
        "additionalProperties": false,
        "properties": {
          "cards": {
            "items": {
              "$ref": "#/components/schemas/EaseEntry"
            },
            "type": "array"
          }
        },
        "required": [
          "cards"
        ],
        "type": "object"
      },
      "SetFlagRequest": {
        "additionalProperties": false,
        "description": "Base body for the batch scheduling verbs.",
        "properties": {
          "card_ids": {
            "description": "A card id sent twice counts once; a missing card is skipped.",
            "items": {
              "type": "integer"
            },
            "type": "array"
          },
          "flag": {
            "maximum": 7.0,
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "card_ids",
          "flag"
        ],
        "type": "object"
      },
      "SetMemoryStateRequest": {
        "additionalProperties": false,
        "properties": {
          "cards": {
            "items": {
              "$ref": "#/components/schemas/MemoryStateEntry"
            },
            "type": "array"
          }
        },
        "required": [
          "cards"
        ],
        "type": "object"
      },
      "SimulateRequest": {
        "additionalProperties": false,
        "description": "Raw passthrough to Anki's SimulateFsrsReviewRequest; omitted limits stay at\nthe protobuf default 0, and Anki itself rejects an unsimulatable setup\n(\"no cards to simulate\"), so there are no invented defaults here. Empty\n`params` means Anki's built-in FSRS defaults.",
        "properties": {
          "days_to_simulate": {
            "default": 365,
            "type": "integer"
          },
          "deck_size": {
            "default": 0,
            "type": "integer"
          },
          "desired_retention": {
            "default": 0.9,
            "type": "number"
          },
          "max_interval": {
            "default": 0,
            "type": "integer"
          },
          "new_limit": {
            "default": 0,
            "type": "integer"
          },
          "params": {
            "default": [],
            "items": {
              "type": "number"
            },
            "type": "array"
          },
          "review_limit": {
            "default": 0,
            "type": "integer"
          },
          "search": {
            "default": "",
            "type": "string"
          }
        },
        "type": "object"
      },
      "SimulateResult": {
        "properties": {
          "accumulated_knowledge_acquisition": {
            "items": {
              "type": "number"
            },
            "type": "array"
          },
          "daily_new_count": {
            "items": {
              "type": "integer"
            },
            "type": "array"
          },
          "daily_review_count": {
            "items": {
              "type": "integer"
            },
            "type": "array"
          },
          "daily_time_cost": {
            "items": {
              "type": "number"
            },
            "type": "array"
          },
          "stats": {
            "type": "object"
          }
        },
        "required": [
          "accumulated_knowledge_acquisition",
          "daily_review_count",
          "daily_new_count",
          "daily_time_cost",
          "stats"
        ],
        "type": "object"
      },
      "SyncEvent": {
        "additionalProperties": false,
        "description": "`sync`: a sync started or finished.",
        "properties": {
          "phase": {
            "enum": [
              "started",
              "finished"
            ],
            "type": "string"
          },
          "seq": {
            "description": "Its number in the session; the SSE `id:` is `<session_id>:<seq>`.",
            "type": "integer"
          },
          "session_id": {
            "description": "The server session; a new one after a restart or profile switch.",
            "type": "string"
          },
          "ts": {
            "description": "When it was sent, Unix milliseconds.",
            "type": "integer"
          },
          "type": {
            "description": "The message's name, also its SSE `event:`.",
            "type": "string"
          }
        },
        "required": [
          "type",
          "seq",
          "session_id",
          "ts",
          "phase"
        ],
        "type": "object"
      },
      "SyncResult": {
        "properties": {
          "server_message": {
            "default": "",
            "type": "string"
          },
          "stats": {
            "type": "object"
          },
          "status": {
            "type": "integer"
          }
        },
        "required": [
          "status"
        ],
        "type": "object"
      },
      "TagBulkRequest": {
        "additionalProperties": false,
        "description": "Body for the batch note verbs, as CardIds is for cards.",
        "properties": {
          "note_ids": {
            "description": "A note id sent twice counts once; a missing note is skipped.",
            "items": {
              "type": "integer"
            },
            "type": "array"
          },
          "tags": {
            "type": "string"
          }
        },
        "required": [
          "note_ids",
          "tags"
        ],
        "type": "object"
      },
      "TagMutationResult": {
        "description": "`affected` counts NOTES changed, which is what Anki's ops report.",
        "properties": {
          "affected": {
            "type": "integer"
          },
          "stats": {
            "type": "object"
          }
        },
        "required": [
          "affected",
          "stats"
        ],
        "type": "object"
      },
      "TagRename": {
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "TagRow": {
        "description": "A tag row. Without `select`, every field is present; with `select`, only those it names. With `shape=scalar`, each item is the selected field's bare value instead.",
        "properties": {
          "name": {
            "description": "Tag name; \"::\" separates levels of a hierarchical tag.",
            "type": "string"
          }
        },
        "type": "object"
      },
      "TemplateCreate": {
        "additionalProperties": false,
        "description": "Schema for creating a template in a note type",
        "properties": {
          "afmt": {
            "type": "string"
          },
          "bafmt": {
            "type": "string"
          },
          "bqfmt": {
            "type": "string"
          },
          "index": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "qfmt": {
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "TemplatePatch": {
        "additionalProperties": false,
        "description": "Schema for patching a template - all fields optional",
        "properties": {
          "afmt": {
            "type": "string"
          },
          "bafmt": {
            "type": "string"
          },
          "bqfmt": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "qfmt": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "TodayStatistics": {
        "description": "Anki's day so far, as the Today section of its Statistics screen counts it.",
        "properties": {
          "correct": {
            "description": "Answered Hard, Good or Easy.",
            "type": "integer"
          },
          "duration_ms": {
            "description": "Time spent answering, in milliseconds.",
            "type": "integer"
          },
          "filtered": {
            "description": "Answers in filtered decks, ahead of schedule.",
            "type": "integer"
          },
          "learn": {
            "description": "Answers to cards in learning.",
            "type": "integer"
          },
          "mature": {
            "$ref": "#/components/schemas/MatureToday"
          },
          "relearn": {
            "description": "Answers to cards in relearning.",
            "type": "integer"
          },
          "review": {
            "description": "Answers to cards in review.",
            "type": "integer"
          },
          "reviews": {
            "description": "Cards answered.",
            "type": "integer"
          },
          "stats": {
            "type": "object"
          },
          "streak": {
            "description": "Days in a row with reviews, up to today; while today has none yet, up to yesterday.",
            "type": "integer"
          }
        },
        "required": [
          "reviews",
          "duration_ms",
          "correct",
          "learn",
          "review",
          "relearn",
          "filtered",
          "mature",
          "streak"
        ],
        "type": "object"
      },
      "UndoResult": {
        "properties": {
          "stats": {
            "type": "object"
          },
          "undone": {
            "description": "The step undone, as Anki names it (\"Add Note\"); null when there was nothing to undo.",
            "type": "string",
            "x-nullable": true
          }
        },
        "type": "object"
      },
      "UpsertMatch": {
        "additionalProperties": false,
        "properties": {
          "field": {
            "description": "Field whose content identifies the note, compared exactly (case-insensitive) within the note type. Default: Anki's duplicate check (the first field, HTML ignored), including duplicateScope and its options.",
            "type": "string"
          }
        },
        "type": "object"
      },
      "ValidationError": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array"
          },
          "msg": {
            "type": "string"
          },
          "type": {
            "type": "string"
          }
        },
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "type": "object"
      },
      "VerbResult": {
        "properties": {
          "affected": {
            "type": "integer"
          },
          "stats": {
            "type": "object"
          }
        },
        "required": [
          "affected",
          "stats"
        ],
        "type": "object"
      },
      "Versions": {
        "properties": {
          "addon": {
            "default": "0.7.1",
            "description": "Tsunagi release version",
            "type": "string"
          },
          "anki": {
            "description": "Running Anki version",
            "type": "string"
          },
          "api": {
            "default": "v1",
            "description": "Native API contract identifier",
            "type": "string"
          }
        },
        "required": [
          "anki"
        ],
        "type": "object"
      },
      "WorkloadResult": {
        "description": "Maps keyed by desired-retention percent, as Anki returns them.",
        "properties": {
          "cost": {
            "additionalProperties": {
              "type": "number"
            },
            "type": "object"
          },
          "memorized": {
            "additionalProperties": {
              "type": "number"
            },
            "type": "object"
          },
          "review_count": {
            "additionalProperties": {
              "type": "number"
            },
            "type": "object"
          },
          "reviewless_end_memorized": {
            "type": "number"
          },
          "stats": {
            "type": "object"
          }
        },
        "required": [
          "cost",
          "memorized",
          "reviewless_end_memorized",
          "review_count",
          "stats"
        ],
        "type": "object"
      },
      "tsunagi__shared__schemas__cards__AnswerRequest": {
        "additionalProperties": false,
        "properties": {
          "answers": {
            "items": {
              "$ref": "#/components/schemas/AnswerEntry"
            },
            "type": "array"
          }
        },
        "required": [
          "answers"
        ],
        "type": "object"
      },
      "tsunagi__shared__schemas__gui__AnswerRequest": {
        "additionalProperties": false,
        "properties": {
          "rating": {
            "description": "Answer button, 1-4",
            "maximum": 4.0,
            "minimum": 1.0,
            "type": "integer"
          }
        },
        "required": [
          "rating"
        ],
        "type": "object"
      }
    },
    "securitySchemes": {
      "ApiKey": {
        "description": "An app's key from Tsunagi settings. Needed from other devices, and wherever a keyless request has No access.",
        "in": "header",
        "name": "X-API-Key",
        "type": "apiKey"
      },
      "BearerAuth": {
        "description": "Alternative to the X-API-Key header.",
        "scheme": "bearer",
        "type": "http"
      }
    }
  },
  "info": {
    "description": "> **Tsunagi 0.7.1, read-only.** To send requests, install Tsunagi and open <http://127.0.0.1:7777/> while Anki runs: the same reference, sending real requests to your collection.\n\n## Overview\n\nTsunagi provides a REST API for your running Anki collection. Browse the endpoints\nin the sidebar, inspect their schemas and code examples, then choose **Test\nRequest** to edit parameters or a JSON body and send the request to Anki.\n\n## Authentication\n\nIf you configured an API key in Tsunagi settings, enter it in **Authentication**.\nNative endpoints accept `X-API-Key` or `Authorization: Bearer <key>`. Leave it blank\nwhen authentication is disabled. The health endpoint is public. AnkiConnect RPC\nuses its own `key` field in the JSON body.\n\n## Core concepts\n\n### Notes, cards and note types\n\nChoose the resource that owns the data you want to work with:\n\n**Notes** contain field values and tags. Use note endpoints to add or edit content.\n\n**Cards** contain scheduling and review state. Use card endpoints to inspect due\ndates or change scheduling.\n\n**Note types** are the field definitions and templates that control card\nstructure and appearance.\n\nA note can generate several cards. For example, a note type with forward and\nreverse templates creates two cards from the same field values. Edit the note\nto change their shared content; work with the cards to change their schedules.\n\n### Find the data you need\n\nFor notes, cards and reviews, `search` accepts the same query syntax as Anki's\nbrowser. Use `tag:verb` to find tagged content, or `deck:Japanese` to restrict a\nquery to a deck.\n\n- **`search`** matches an Anki browser query, such as `tag:verb`.\n- **`where`** filters resource fields: `modified>1790000000`, `name~=Basic` (names\n  containing “Basic”), `note_type_name in [\"Kiku\",\"Kiku+\"]`. Several `where`\n  clauses must all match.\n- **`select`** chooses returned fields: `select=id,name` returns\n  `[{\"id\": 1, \"name\": \"Basic\"}, …]`, and `select=id` returns `[{\"id\": 1}, …]`.\n  Add `shape=scalar` to get one field's bare values instead: `[1, …]`.\n- **`order`** sorts: `order=due` or `order=note_modified:desc`. Cards and notes\n  use Anki's Browser sorts, by their Browser names (`due`, `interval`, `ease`,\n  `lapses`, `reviews`, `created`, `note_modified`, `deck`, `note_type`,\n  `sort_field`, …), so `due` orders new, learning and review cards as the\n  Browser does. Row fields that sort the same work too: `reps`, `modified`, and `id`\n  on notes. Other resources sort by a field of their rows, such as\n  `order=id:desc` for the newest reviews. Without `order`, rows come in\n  ascending id.\n- **`distinct_on`** keeps one row per value of a field, the first in `order`:\n  `GET /v1/reviews?distinct_on=card_id&order=id:desc` is each card's latest\n  review, and `distinct_on=note_id` on cards is one card per note. `where`\n  applies first. On cards, notes and reviews it takes a column field, and every\n  `where` clause must be on a column too; use `search` for the rest.\n- **`include`** adds extra parts, comma-separated. Each costs a step before\n  the answer, so none is sent unless asked: `total` on lists (how many rows the\n  query matches across all pages; with `limit=0`, the count alone), and\n  `cards` and `duplicate_ids` on note writes and checks.\n- **`limit`** sets the page size, such as `10`.\n- **`cursor`** continues a query using the response's `next_cursor`.\n\nA `where` clause is a field, an operator (`==`, `!=`, `~=`, `^=`, `$=`, `>`,\n`>=`, `<`, `<=`, `in`, `not in`) and a value. `~=` contains, ignoring case; `^=`\nstarts with and `$=` ends with, matching case as `==` does\n(`GET /v1/tags?where=name^=\"Japanese::\"`, `GET /v1/media?where=filename$=\".mp3\"`).\nNumbers, `true`, `false` and `null` are written as in JSON. **Write text as a JSON string**, and a list as a JSON array, so any\nname is safe, including quotes and backslashes: build the value with your\nlanguage's JSON encoder, such as `\"note_type_name in \" + JSON.stringify(names)` in\nJavaScript or `\"note_type_name in \" + json.dumps(names)` in Python.\n\nCheck each endpoint for its supported fields and filters. In the request\nconsole, enter parameter values directly; the client handles URL encoding.\n\n### Choosing rows: `search` or `where`\n\nUse `search` to choose cards and notes: it is Anki's own search, fast and with\nAnki's rules. Use `where` for what search can't do: exact values in the rows\nyou get back, and resources Anki can't search, such as reviews and decks.\n\n- Notes of a note type: `search=note:Kiku`\n- Cards in a deck and its subdecks: `search=deck:Mining`\n- Suspended cards: `search=is:suspended`\n- Cards with an interval of 21 days or more: `search=prop:ivl>=21`\n- Notes edited in the last 2 days: `search=edited:2`\n- Notes edited since an exact moment: `search=edited:2&where=modified>=1790000000`\n  (the search narrows quickly; `where` makes it exact to the second)\n- Notes whose first field is a word: `where=first_field in [\"食べる\"]`, with\n  `search=deck:Mining` to look in one deck only\n- Reviews answered Again: `where=rating==1` on `/v1/reviews`; there, `search`\n  chooses the cards whose reviews you get\n\n### GET parameters or a JSON body\n\nMany list endpoints also provide a **Query … (POST)** operation. Both forms\nperform the same query. For example, these requests find up to ten notes tagged\n`verb` and return their IDs:\n\n```http\nGET /v1/notes?search=tag%3Averb&select=id&limit=10\n```\n\nFor `POST /v1/notes/query`, enter this JSON body:\n\n```json\n{\n  \"search\": \"tag:verb\",\n  \"select\": \"id\",\n  \"limit\": 10\n}\n```\n\nUse the POST query form when a JSON body is easier to compose. **Create note**\nis a separate operation at `POST /v1/notes`.\n\n### Read the next page\n\nA paginated response has three parts:\n\n- **`items`** holds the results on this page, shaped by your query.\n- **`next_cursor`** is the token for the following page. A value of `null` means\n  there are no more results.\n- **`stats`** contains metadata about how the query was processed.\n\nTo continue, copy `next_cursor` into the next request's `cursor` parameter or\nJSON property. Keep the search, filters, field selection and page size the same.\nTreat the cursor as an opaque token: copy it as returned. Clear it when starting\na different query. A cursor belongs to the profile it came from: after a switch\nto another profile it's refused (400), so start over. A small `limit`, such as `10`, makes initial requests easier\nto inspect.\n\n### Check feature support and job status\n\n`GET /v1/capabilities` reports all native operations in one place. Each operation\nhas a `status`: `available`, `disabled` for your app (its role lacks the\npermission, or the app is turned off), or `unsupported` by this Anki version.\nConditional options carry the same status, plus a reason and the permission or\nsetting that controls them. AnkiConnect actions remain separate at `GET /actions`.\n\nLook up an operation by method and path, such as\n`operations[\"POST /v1/cards:set-memory-state\"]`. FSRS scheduling appears under\n`features.fsrs_scheduling`; computations that work while scheduling is off remain\navailable. See [native discovery](https://github.com/mcgrizzz/Tsunagi/blob/main/docs/capabilities.md)\nfor the response layout.\n\nFor an operation that returns an asynchronous job, use its returned job ID to\npoll `GET /v1/jobs/{job_id}` for status and results.\n\n### Errors\n\nTsunagi API route and validation errors are JSON with `detail`, a message you\ncan show. A 422 also lists each problem in `errors`, and a 503 says why in\n`reason`: `busy`, `syncing` or `closed`. A request refused for its Host header\nor website origin gets a plain-text 403 instead. The status says what to do:\n\n| Status | Means | What to do |\n| --- | --- | --- |\n| 200, 201 | Done; 201 created something | |\n| 202 | Accepted as a job | Poll `GET /v1/jobs/{job_id}` |\n| 400 | The request's values are wrong: an unknown field, a bad search or cursor | Fix the request |\n| 401 | No usable key | Send a valid key |\n| 403 | The app's role lacks the permission (`detail` names it), or the app is off | Change the role on the settings page |\n| 404 | What you named doesn't exist | |\n| 409 | Valid, but it clashes with the collection: a name or id that's taken, a duplicate note, a job already running, a full sync Anki must do | Change the request, or wait |\n| 422 | The body or parameters don't match the operation | Fix the request (`errors` lists each problem) |\n| 500 | Tsunagi failed | Report it: it's a bug |\n| 501 | This Anki version lacks the feature | Update Anki |\n| 502 | AnkiWeb refused or couldn't be reached | Retry later |\n| 503 | Anki is busy, syncing or has no profile open (`reason`) | Retry; a write may still finish |\n\nA 503 doesn't cancel a write: Anki may still finish it. Send an\n`Idempotency-Key` header (a new UUID per request) with any write, and the same\nkey when you retry it: the retry gets the first attempt's result, marked\n`Idempotent-Replayed: true`, instead of writing again. A retried job gets the\nsame job, so it never starts twice; a job that failed starts again.\n\n## Try a request\n\nStart with `GET /v1/health`, or **Notes → List notes** with `limit=10`. Health\nneeds no key or open profile. Its `caller` says who the request counts as (app,\nrole) and whether its key is `valid`, `unknown` (counts as no key) or `none`,\nso sending your key checks it.\nThe console sends real requests to your current Anki profile, including changes\nwhen you use a write operation.\n\n[OpenAPI JSON](openapi.json) · [Tsunagi on GitHub](https://github.com/mcgrizzz/Tsunagi)\n",
    "license": {
      "name": "MIT"
    },
    "title": "Tsunagi",
    "version": "0.7.1"
  },
  "openapi": "3.1.0",
  "paths": {
    "/": {
      "get": {
        "description": "Scalar API reference and interactive request console",
        "operationId": "rootLandingPage",
        "responses": {
          "200": {
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "Successful Response"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "summary": "Tsunagi API landing page",
        "tags": [
          "Health"
        ],
        "x-permission": "public"
      },
      "post": {
        "description": "AnkiConnect-compatible POST endpoint at root. Send action and params in JSON body.",
        "operationId": "ankiConnectRpc",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {}
              }
            },
            "description": "Successful Response"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "summary": "AnkiConnect RPC endpoint",
        "tags": [
          "AnkiConnect Compatibility"
        ],
        "x-permission": "public"
      }
    },
    "/actions": {
      "get": {
        "description": "Get a list of all registered AnkiConnect-compatible actions",
        "operationId": "listAnkiConnectActions",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "Successful Response"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "List available AnkiConnect actions",
        "tags": [
          "AnkiConnect Compatibility"
        ],
        "x-permission": "public"
      }
    },
    "/v1/addons": {
      "get": {
        "description": "Every installed add-on, enabled or not, sorted by folder name.",
        "operationId": "listAddons",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AddonList"
                }
              }
            },
            "description": "Successful Response"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "List installed add-ons",
        "tags": [
          "Add-ons"
        ],
        "x-permission": "read:addons"
      }
    },
    "/v1/addons/{addon_id}": {
      "get": {
        "operationId": "getAddon",
        "parameters": [
          {
            "description": "The add-on's folder name, as listed by GET /v1/addons",
            "in": "path",
            "name": "addon_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AddonInfo"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Get an add-on",
        "tags": [
          "Add-ons"
        ],
        "x-permission": "read:addons"
      }
    },
    "/v1/addons/{provider_id}/actions": {
      "get": {
        "description": "What a provider offers for its add-on: reads return data, other actions run as jobs. `provider_id` is the provider's id (`provider` in GET /v1/addons), the same on every install.",
        "operationId": "listAddonActions",
        "parameters": [
          {
            "$ref": "#/components/parameters/path.provider_id"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionList"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "List an add-on's actions",
        "tags": [
          "Add-ons"
        ],
        "x-permission": "read:addons"
      }
    },
    "/v1/addons/{provider_id}/actions/{name}:run": {
      "post": {
        "description": "The body holds the action's parameters. A read answers 200 with its data. Any other action answers 202 with a job; poll GET /v1/jobs/{id}. Its result is `{provider, action, result, settled, backup}`: `settled` is false if the add-on's follow-up work did not end within two minutes, `backup` names the Anki backup made before a destructive action. Actions cannot be aborted. The user must enable the action in Tsunagi's settings, and the caller needs `addon:<provider>/<name>`; reads need only read:addons. 409 if the add-on is unavailable or another job is running.",
        "operationId": "runAddonAction",
        "parameters": [
          {
            "$ref": "#/components/parameters/path.provider_id"
          },
          {
            "in": "path",
            "name": "name",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ActionResult"
                    },
                    {
                      "$ref": "#/components/schemas/JobSubmitted"
                    }
                  ]
                }
              }
            },
            "description": "Successful Response"
          },
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobSubmitted"
                }
              }
            },
            "description": "The action runs as a job"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Run an add-on action",
        "tags": [
          "Add-ons"
        ],
        "x-permission": "addon"
      }
    },
    "/v1/capabilities": {
      "get": {
        "description": "One report for all native operations and collection features. Each has an effective status: available, disabled in settings, or unsupported by this Anki. Conditional options carry their own status, reason and setting. Availability does not bypass input validation, authentication or transient collection/GUI state. Needs no permission, so an app can see what its role is missing; features that read the collection or the installed add-ons show as disabled, naming the permission, for an app without it. Requires an open collection; /v1/health remains a lightweight liveness check. AnkiConnect actions are listed separately at /actions.",
        "operationId": "getCapabilities",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Capabilities"
                }
              }
            },
            "description": "Successful Response"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Native API capabilities",
        "tags": [
          "Collection"
        ],
        "x-permission": "public"
      }
    },
    "/v1/cards": {
      "get": {
        "description": "Query cards with filtering, field selection, and pagination. Cards are generated from notes by a note type's templates. Use `search` for Anki query syntax; scheduling changes go through the verb routes.",
        "operationId": "listCards",
        "parameters": [
          {
            "$ref": "#/components/parameters/query.select"
          },
          {
            "$ref": "#/components/parameters/query.where"
          },
          {
            "description": "Anki search string (e.g. 'deck:Japanese tag:verb').",
            "in": "query",
            "name": "search",
            "required": false,
            "schema": {
              "type": "string",
              "x-from": [
                "notes",
                "decks",
                "note_types",
                "tags"
              ]
            }
          },
          {
            "description": "Sort: a name, optionally with :asc (default) or :desc, e.g. due:desc. Cards and notes use Anki's Browser sorts (due, interval, ease, lapses, reviews, created, card_modified, note_modified, deck, note_type, sort_field, tags, position, card_type; difficulty, stability and retrievability on cards), and the row fields that sort the same (reps, modified; id on notes); other resources a field of their rows. Ties in ascending id. Without it, rows come in ascending id.",
            "in": "query",
            "name": "order",
            "required": false,
            "schema": {
              "type": "string",
              "x-sorts": [
                "card_modified",
                "card_type",
                "created",
                "deck",
                "difficulty",
                "due",
                "ease",
                "interval",
                "lapses",
                "modified",
                "note_modified",
                "note_type",
                "position",
                "reps",
                "retrievability",
                "reviews",
                "sort_field",
                "stability",
                "tags"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/query.distinct_on"
          },
          {
            "$ref": "#/components/parameters/query.shape"
          },
          {
            "$ref": "#/components/parameters/query.include"
          },
          {
            "$ref": "#/components/parameters/query.limit"
          },
          {
            "$ref": "#/components/parameters/query.cursor"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Paginated_CardRow_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "List cards",
        "tags": [
          "Cards"
        ],
        "x-permission": "read:cards"
      }
    },
    "/v1/cards/query": {
      "post": {
        "description": "Query cards using request body. Supports complex queries with filtering and field selection. Omit cursor or use null to start at page one; malformed or empty cursors return 400.",
        "operationId": "queryCards",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/QueryRequest"
                  }
                ],
                "description": "Query parameters in request body"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Paginated_CardRow_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Query cards (POST)",
        "tags": [
          "Cards"
        ],
        "x-permission": "read:cards"
      }
    },
    "/v1/cards:answer": {
      "post": {
        "description": "Answers each card through the scheduler as if the button (`rating` 1-4: again/hard/good/easy) had been pressed in the reviewer. Works on a card in any state - answering a suspended card unsuspends it. A missing card is skipped; `affected` counts the cards answered.",
        "operationId": "cardsAnswer",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/tsunagi__shared__schemas__cards__AnswerRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerbResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Answer cards",
        "tags": [
          "Cards"
        ],
        "x-permission": "write:cards"
      }
    },
    "/v1/cards:batch": {
      "post": {
        "description": "Runs the listed scheduling verbs in order as a single undo entry (\"Card Batch\") - one Ctrl+Z in Anki reverts the whole batch, and the event stream sees one `op` carrying every card id involved. Each entry is `{\"op\": \"<verb>\", ...that verb's body}`, where `<verb>` is one of: bury, change-deck, forget, reposition, set-due-date, set-ease, set-flag, suspend, unbury, unsuspend. Everything checkable is validated before any write; a backend error mid-run (rare) leaves the earlier steps applied with the undo history cleared - the batch is one undo entry, not a transaction. `answer`, `set-values` and `set-memory-state` are deliberately not batchable.",
        "operationId": "cardsBatch",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Run several scheduling verbs as one undoable operation",
        "tags": [
          "Cards"
        ],
        "x-permission": "write:cards"
      }
    },
    "/v1/cards:bury": {
      "post": {
        "description": "Hides cards until the next day.",
        "operationId": "cardsBury",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CardIds"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerbResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Bury cards",
        "tags": [
          "Cards"
        ],
        "x-permission": "write:cards"
      }
    },
    "/v1/cards:change-deck": {
      "post": {
        "description": "Takes deck_id or deck_name. The deck must already exist - this never creates one.",
        "operationId": "cardsChangeDeck",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChangeDeckRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerbResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Move cards to another deck",
        "tags": [
          "Cards"
        ],
        "x-permission": "write:cards"
      }
    },
    "/v1/cards:delete-empty": {
      "post": {
        "description": "Anki's Tools > Empty Cards: deletes the cards whose template shows nothing, in the whole collection, as one undo step. A note whose cards are all empty keeps its first card unless `keep_notes` is false, which deletes the note (and needs `write:notes`). With `dry_run`, answers what would be deleted and deletes nothing.",
        "operationId": "cardsDeleteEmpty",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmptyCardsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmptyCardsResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Delete empty cards",
        "tags": [
          "Cards"
        ],
        "x-permission": "write:cards"
      }
    },
    "/v1/cards:forget": {
      "post": {
        "description": "Puts cards back in the new queue, optionally restoring their original position.",
        "operationId": "cardsForget",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ForgetRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerbResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Reset cards to new",
        "tags": [
          "Cards"
        ],
        "x-permission": "write:cards"
      }
    },
    "/v1/cards:reposition": {
      "post": {
        "description": "Reassigns the position (`due`) of cards in the new queue.",
        "operationId": "cardsReposition",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RepositionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerbResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Reposition new cards",
        "tags": [
          "Cards"
        ],
        "x-permission": "write:cards"
      }
    },
    "/v1/cards:set-due-date": {
      "post": {
        "description": "`days` is '5' (due in five days) or '5-7' (a random day in that range).",
        "operationId": "cardsSetDueDate",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetDueDateRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerbResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Set the due date",
        "tags": [
          "Cards"
        ],
        "x-permission": "write:cards"
      }
    },
    "/v1/cards:set-ease": {
      "post": {
        "description": "Per-card ease, stored as an integer 10x the percentage (250% is 2500).",
        "operationId": "cardsSetEase",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetEaseRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerbResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Set ease factors",
        "tags": [
          "Cards"
        ],
        "x-permission": "write:cards"
      }
    },
    "/v1/cards:set-flag": {
      "post": {
        "description": "0 clears the flag; 1-7 are Anki's colours.",
        "operationId": "cardsSetFlag",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetFlagRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerbResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Set the coloured flag",
        "tags": [
          "Cards"
        ],
        "x-permission": "write:cards"
      }
    },
    "/v1/cards:set-memory-state": {
      "post": {
        "description": "Overwrites per-card FSRS state (stability/difficulty, desired retention, decay) - how FSRS helper tools reschedule. An omitted field is left unchanged; an explicit null clears it. Needs the memory_state permission, which only the Everything role has by default.",
        "operationId": "cardsSetMemoryState",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetMemoryStateRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerbResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Set FSRS memory state",
        "tags": [
          "Cards"
        ],
        "x-permission": "memory_state"
      }
    },
    "/v1/cards:set-values": {
      "post": {
        "description": "Writes card columns as-is, with no validation beyond the column's type - the escape hatch AnkiConnect calls setSpecificValueOfCard. Columns: did, due, factor, flags, ivl, lapses, left, nid, odid, odue, ord, queue, reps, type; each value an integer. Scheduling and linkage columns (did, ivl, lapses, left, nid, odid, odue, ord, queue, reps, type) corrupt the card when written badly, so they require `force: true`.",
        "operationId": "cardsSetValues",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetCardValuesRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerbResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Set raw card columns",
        "tags": [
          "Cards"
        ],
        "x-permission": "write:cards"
      }
    },
    "/v1/cards:suspend": {
      "post": {
        "description": "Removes cards from review until unsuspended.",
        "operationId": "cardsSuspend",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CardIds"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerbResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Suspend cards",
        "tags": [
          "Cards"
        ],
        "x-permission": "write:cards"
      }
    },
    "/v1/cards:unbury": {
      "post": {
        "description": "Returns buried cards to their queue.",
        "operationId": "cardsUnbury",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CardIds"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerbResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Unbury cards",
        "tags": [
          "Cards"
        ],
        "x-permission": "write:cards"
      }
    },
    "/v1/cards:unsuspend": {
      "post": {
        "description": "Returns suspended cards to their queue.",
        "operationId": "cardsUnsuspend",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CardIds"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerbResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Unsuspend cards",
        "tags": [
          "Cards"
        ],
        "x-permission": "write:cards"
      }
    },
    "/v1/collection": {
      "get": {
        "description": "Collection-wide facts with no row of their own: whether FSRS is enabled (one switch for the whole collection - per-deck and per-preset `desired_retention` values exist either way, the scheduler just ignores them while this is off), and the running Anki version for feature detection.",
        "operationId": "getCollectionMeta",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CollectionMeta"
                }
              }
            },
            "description": "Successful Response"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Collection metadata",
        "tags": [
          "Collection"
        ],
        "x-permission": "read:collection"
      }
    },
    "/v1/collection/import-options": {
      "get": {
        "description": "Returns Anki's saved import choices for the open collection. Unsupported options are listed separately and have null values. Reading does not import a file or change preferences. Pass explicit options to /v1/collection:import to keep a reviewed choice fixed even if preferences change before the import.",
        "operationId": "getImportOptions",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportPreferences"
                }
              }
            },
            "description": "Successful Response"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Read saved package import options",
        "tags": [
          "Collection"
        ],
        "x-permission": "read:collection"
      }
    },
    "/v1/collection:check-database": {
      "post": {
        "description": "Runs Anki's Check Database, which rebuilds indexes and fixes inconsistencies.",
        "operationId": "checkDatabase",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CollectionActionResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Check the database",
        "tags": [
          "Collection"
        ],
        "x-permission": "manage"
      }
    },
    "/v1/collection:export": {
      "post": {
        "description": "Writes one deck to an .apkg file. The path is resolved on the machine running Anki, not the caller's.",
        "operationId": "exportPackage",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExportRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CollectionActionResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Export a deck",
        "tags": [
          "Collection"
        ],
        "x-permission": "manage"
      }
    },
    "/v1/collection:import": {
      "post": {
        "description": "Merges an .apkg into the current collection. The path is resolved on the machine running Anki. Imports immediately without an options dialog. Omitted or null options use Anki's saved import preferences, available at GET /v1/collection/import-options. Explicit values override those choices. Anki remembers the resulting options after a successful import. Unsupported explicit options are rejected before importing. Returns the result with HTTP 200 if the import completes within the operation timeout; otherwise returns HTTP 202 with a job_id and Location header. Poll GET /v1/jobs/{job_id}; the same import continues without restarting. Import jobs cannot be aborted through the API. Only one import or FSRS job may be active at once (409 otherwise). Jobs are kept in memory only.",
        "operationId": "importPackage",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImportRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobSubmitted"
                }
              }
            },
            "description": "Import still running; poll the job"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Import a package",
        "tags": [
          "Collection"
        ],
        "x-permission": "manage"
      }
    },
    "/v1/collection:sync": {
      "post": {
        "description": "Syncs with AnkiWeb using the account already set up in Anki, the way Anki's Sync button does: add-ons that react to a sync (for example FSRS Helper's reschedule after sync) run, media syncs, and Anki's window stays responsive and refreshes afterwards. It never shows a dialog on the computer. Answers with the result when the sync ends within op_timeout_seconds, otherwise 202 with a job to poll (not abortable). 400 if no sync account is set up; 409 if the collection needs a full upload or download, which Anki asks the user about (click Sync in Anki); 502 if AnkiWeb or the network refused the sync. Runs as the one job, so another running job is also a 409.",
        "operationId": "syncCollection",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SyncResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobSubmitted"
                }
              }
            },
            "description": "Sync still running; poll the job"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Sync the collection",
        "tags": [
          "Collection"
        ],
        "x-permission": "sync"
      }
    },
    "/v1/deck-presets": {
      "get": {
        "description": "Query deck presets with filtering, field selection, and pagination. Deck options presets. Rows are Anki's config dicts verbatim, so newer scheduler keys survive a read-modify-write. Assign one to a deck with PATCH /v1/decks/{id} {preset_id}.",
        "operationId": "listDeckPresets",
        "parameters": [
          {
            "$ref": "#/components/parameters/query.select"
          },
          {
            "$ref": "#/components/parameters/query.where"
          },
          {
            "description": "Sort: a name, optionally with :asc (default) or :desc, e.g. due:desc. Cards and notes use Anki's Browser sorts (due, interval, ease, lapses, reviews, created, card_modified, note_modified, deck, note_type, sort_field, tags, position, card_type; difficulty, stability and retrievability on cards), and the row fields that sort the same (reps, modified; id on notes); other resources a field of their rows. Ties in ascending id. Without it, rows come in ascending id.",
            "in": "query",
            "name": "order",
            "required": false,
            "schema": {
              "type": "string",
              "x-sorts": [
                "id",
                "name"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/query.distinct_on"
          },
          {
            "$ref": "#/components/parameters/query.shape"
          },
          {
            "$ref": "#/components/parameters/query.include"
          },
          {
            "$ref": "#/components/parameters/query.limit"
          },
          {
            "$ref": "#/components/parameters/query.cursor"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Paginated_DeckPresetRow_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "List deck presets",
        "tags": [
          "Deck Presets"
        ],
        "x-permission": "read:deck_presets"
      },
      "post": {
        "description": "Create a new deck preset. Deck options presets. Rows are Anki's config dicts verbatim, so newer scheduler keys survive a read-modify-write. Assign one to a deck with PATCH /v1/decks/{id} {preset_id}.",
        "operationId": "createDeckPreset",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "description": "Deck preset data to create",
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MutationResult_Any_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Create deck preset",
        "tags": [
          "Deck Presets"
        ],
        "x-permission": "write:deck_presets"
      }
    },
    "/v1/deck-presets/query": {
      "post": {
        "description": "Query deck presets using request body. Supports complex queries with filtering and field selection. Omit cursor or use null to start at page one; malformed or empty cursors return 400.",
        "operationId": "queryDeckPresets",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/QueryRequest"
                  }
                ],
                "description": "Query parameters in request body"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Paginated_DeckPresetRow_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Query deck presets (POST)",
        "tags": [
          "Deck Presets"
        ],
        "x-permission": "read:deck_presets"
      }
    },
    "/v1/deck-presets/{id}": {
      "delete": {
        "description": "Delete a deck preset.",
        "operationId": "deleteDeckPreset",
        "parameters": [
          {
            "description": "Deck preset ID to delete",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeletionResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Delete deck preset",
        "tags": [
          "Deck Presets"
        ],
        "x-permission": "write:deck_presets"
      },
      "patch": {
        "description": "Partially update a deck preset. Only provided fields will be updated.",
        "operationId": "updateDeckPreset",
        "parameters": [
          {
            "description": "Deck preset ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "description": "Fields to update",
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MutationResult_Any_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Update deck preset",
        "tags": [
          "Deck Presets"
        ],
        "x-permission": "write:deck_presets"
      }
    },
    "/v1/decks": {
      "get": {
        "description": "Query decks with filtering, field selection, and pagination. Deck hierarchy (nested names use '::'). Filtered (dynamic) decks appear in reads; mutations operate on normal decks.",
        "operationId": "listDecks",
        "parameters": [
          {
            "$ref": "#/components/parameters/query.select"
          },
          {
            "$ref": "#/components/parameters/query.where"
          },
          {
            "description": "Sort: a name, optionally with :asc (default) or :desc, e.g. due:desc. Cards and notes use Anki's Browser sorts (due, interval, ease, lapses, reviews, created, card_modified, note_modified, deck, note_type, sort_field, tags, position, card_type; difficulty, stability and retrievability on cards), and the row fields that sort the same (reps, modified; id on notes); other resources a field of their rows. Ties in ascending id. Without it, rows come in ascending id.",
            "in": "query",
            "name": "order",
            "required": false,
            "schema": {
              "type": "string",
              "x-sorts": [
                "browser_collapsed",
                "collapsed",
                "description",
                "desired_retention",
                "dynamic",
                "extend_new",
                "extend_rev",
                "id",
                "learn_count",
                "modified",
                "name",
                "new_count",
                "new_limit",
                "new_limit_today",
                "new_limit_used",
                "preset_id",
                "review_count",
                "review_limit",
                "review_limit_today",
                "review_limit_used",
                "study_ms_today",
                "total_in_deck",
                "usn"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/query.distinct_on"
          },
          {
            "$ref": "#/components/parameters/query.shape"
          },
          {
            "$ref": "#/components/parameters/query.include"
          },
          {
            "$ref": "#/components/parameters/query.limit"
          },
          {
            "$ref": "#/components/parameters/query.cursor"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Paginated_DeckRow_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "List decks",
        "tags": [
          "Decks"
        ],
        "x-permission": "read:decks"
      },
      "post": {
        "description": "Create a new deck. Deck hierarchy (nested names use '::'). Filtered (dynamic) decks appear in reads; mutations operate on normal decks.",
        "operationId": "createDeck",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/DeckCreate"
                  }
                ],
                "description": "Deck data to create"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MutationResult_Any_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Create deck",
        "tags": [
          "Decks"
        ],
        "x-permission": "write:decks"
      }
    },
    "/v1/decks/query": {
      "post": {
        "description": "Query decks using request body. Supports complex queries with filtering and field selection. Omit cursor or use null to start at page one; malformed or empty cursors return 400.",
        "operationId": "queryDecks",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/QueryRequest"
                  }
                ],
                "description": "Query parameters in request body"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Paginated_DeckRow_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Query decks (POST)",
        "tags": [
          "Decks"
        ],
        "x-permission": "read:decks"
      }
    },
    "/v1/decks/{id}": {
      "delete": {
        "description": "Delete a deck.",
        "operationId": "deleteDeck",
        "parameters": [
          {
            "description": "Deck ID to delete",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeletionResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Delete deck",
        "tags": [
          "Decks"
        ],
        "x-permission": "write:decks"
      },
      "patch": {
        "description": "Partially update a deck. Only provided fields will be updated.",
        "operationId": "updateDeck",
        "parameters": [
          {
            "description": "Deck ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/DeckPatch"
                  }
                ],
                "description": "Fields to update"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MutationResult_Any_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Update deck",
        "tags": [
          "Decks"
        ],
        "x-permission": "write:decks"
      }
    },
    "/v1/events": {
      "get": {
        "description": "Streams named collection events as Server-Sent Events (`text/event-stream`).\n\nConnect with `?resources=notes` for note events:\n\n- `notes.created`: notes were added; `ids` contains their IDs.\n- `notes.updated`: a note update completed; `ids` identifies the affected notes.\n- `notes.deleted`: a deletion completed; these `ids` are now absent.\n- `notes.stale`: notes changed, but which ones isn't known. It has no `ids`;\n  reload the notes you show.\n\nCards use the same names with the cards prefix. Reviews report reviews.created\nwith review log IDs. decks.counts lists the decks whose due counts changed\n(answers, suspends, deck changes, syncs, day rollover), each as {deck_id, new_count,\nlearn_count, review_count, total_in_deck} like /v1/decks rows. Other resources currently report stale notifications.\nday.changed means Anki's day changed: searches relative to today (is:due, is:buried,\nrated:1, prop:due=0) can now match other rows, though no row was written, so run them\nagain. It comes when a new day starts, and when changing \"next day starts at\" (in\nAnki's preferences or by sync) moves the day, even back. It comes with every change stream, whatever its resources (types=change, the\ndefault with resources, or types=day.changed), and needs events:changes like them.\nNo full notes, card contents or media are sent. Fetch any contents your app needs through the normal API, with select to choose fields.\nA resource with complete ID details does not also emit stale for that operation.\nRelated resources can produce separate events: deleting a note produces\nnotes.deleted and cards.deleted. Each notification has its own sequence number.\n\n### Coverage\n\nNative and compatibility note creation/field updates provide note IDs. Native\ncreation also provides generated card IDs. Note deletion provides IDs now absent,\nfor the notes and their cards; a batch may include note IDs already absent. Individual suspend/unsuspend/bury/unbury\noperations report cards.updated with their processed IDs; a batch can include\ncards already in the requested state. Empty/no-op operations produce no event.\nChanges made inside Anki (Browser, editor, Add dialog, reviewer) are reported\nwith IDs about 0.5 s after they stop, or after a 2 s pause in typing, including\nreviews.created for new review log rows, and one decks.counts per burst. Undo and other operations whose rows\ncan't be identified use resource.stale. ID lists above 1,000 per resource\nalso use stale.\n\n### Filters\n\n- resources: comma-separated notes, cards, note_types, decks, tags, reviews,\n  scheduler, config. Selects events about those resources, including related\n  query changes (such as a deck rename affecting a note search).\n- types: exact names such as notes.created, notes.updated, notes.deleted,\n  notes.stale, cards.updated, cards.answered, sync. The group change selects all data\n  event types. Omit both filters for all activity. Resources alone selects data\n  events; types alone can select just one event, such as notes.deleted.\n- Combined filters intersect for data events. cards.answered/sync can be selected alongside\n  them. A combination with no matching data type returns HTTP 422, as do empty\n  or unknown filter values. Filtering happens before the subscriber queue.\n\ncards.answered is sent for answers in Anki's reviewer (by ui) and through either API\n(by api). It contains card_id, rating (1 Again, 2 Hard, 3 Good, 4 Easy) and\nthe card's new interval, due, queue and memory_state, named as in card rows.\nsync contains\nphase started/finished. Exact type filters omit other events, including stale;\nuse resources alone if you need all notifications affecting a displayed list.\n\n### Connection events\n\nEvery connection starts with ready: session_id, after_seq, ts, the selected\ndata resources and heartbeat_ms, how often a heartbeat comment is sent\n(15000). This announces an active subscription, not a data change. Load\ninitial data after ready. cards.answered/sync-only subscriptions have an empty resources\nlist. A new connection gets a new ready even when it uses the same session.\n\naccess.changed (type and ts only) means what this app may do might have\nchanged: fetch /v1/capabilities again. Every stream gets it, whatever its\nfilters: before close with reason auth when this app's key, role or the role's\npermissions change, and on an open stream when add-on actions are enabled or\ndisabled or FSRS is turned on or off. types=access.changed alone needs no\npermission, so any accepted app can watch for it; other filters need\nread:collection. The capabilities report lists what this stream would send\nthe app under operations[\"GET /v1/events\"].options.\n\ngap means queued notifications were lost (reason lagged, discarded count).\nData clients can reload their displayed queries; answer counters should mark\ndelivery incomplete. There is no separate refresh instruction or acknowledgement.\nBroad Anki resets report resource.stale with reason collection. Other unknown\nchanges use reason details_unavailable. No whole-collection download is required.\n\n### Ordering and connections\n\nLive events carry session_id, seq and ts (Unix milliseconds); SSE ID is\n<session_id>:<seq>. Ready and gap carry after_seq and no SSE ID. Registration\nand the ready boundary are atomic; queued live events have larger seq. Filtering\ncan leave normal sequence gaps. One operation can emit several named events.\n\nHTTP reads return current data, not historical snapshots. Coordinate overlapping\nreads so an older response cannot overwrite a newer one. Repeat filtered queries\nwhen membership, sorting or counts might change. Ignore unfinished loads after\ndisconnecting.\n\nDelivery is best-effort and live-only: Last-Event-ID does not replay events.\nReload relevant data after reconnecting. Restarts/profile switches create a new\nsession and close old streams. close reports profile_closed (reconnect once\na profile is open again), shutdown, auth (the caller's key or role changed), timeout or max_events. Ready and gap do not count toward max_events. Heartbeat\ncomments keep idle connections alive. No active session returns HTTP 503.\nBrowser EventSource can use api_key when it cannot set an authentication header.\n\nGeneral and detailed Add-dialog notifications can overlap. Media/import coverage\nis incomplete; other add-ons can bypass hooks. Optional by/app/anki fields are\ndiagnostics, not stable identifiers for user actions. Changes made through the\nAPI carry app, the name of the app that sent the request.",
        "operationId": "streamEvents",
        "parameters": [
          {
            "description": "Close the stream cleanly after this many seconds (for scripts and polling clients).",
            "in": "query",
            "name": "timeout",
            "required": false,
            "schema": {
              "exclusiveMinimum": 0.0,
              "type": "number"
            }
          },
          {
            "description": "Close the stream cleanly after this many notifications (ready and gap do not count).",
            "in": "query",
            "name": "max_events",
            "required": false,
            "schema": {
              "exclusiveMinimum": 0.0,
              "type": "integer"
            }
          },
          {
            "description": "Alternative to the X-Api-Key header for clients that cannot send headers (browser EventSource). Checked by the auth middleware.",
            "in": "query",
            "name": "api_key",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Comma-separated event names, such as notes.updated, notes.deleted, notes.stale, cards.answered, sync; change selects all data events. Defaults to change when resources is supplied, otherwise all.",
            "in": "query",
            "name": "types",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Views to keep current, comma-separated: notes, cards, note_types, decks, tags, reviews, scheduler, config. Selects changes unless types is explicit. Combined types must match at least one selected data resource.",
            "in": "query",
            "name": "resources",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {}
              },
              "text/event-stream": {}
            },
            "description": "An SSE stream of collection events."
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Stream collection events",
        "tags": [
          "Events"
        ],
        "x-events": {
          "access.changed": {
            "schema": "#/components/schemas/AccessChanged"
          },
          "cards.answered": {
            "option": "cards.answered",
            "schema": "#/components/schemas/CardAnswered"
          },
          "cards.created": {
            "resource": "cards",
            "schema": "#/components/schemas/RowsChanged"
          },
          "cards.deleted": {
            "resource": "cards",
            "schema": "#/components/schemas/RowsChanged"
          },
          "cards.stale": {
            "resource": "cards",
            "schema": "#/components/schemas/RowsStale"
          },
          "cards.updated": {
            "resource": "cards",
            "schema": "#/components/schemas/RowsChanged"
          },
          "close": {
            "schema": "#/components/schemas/Close"
          },
          "config.stale": {
            "resource": "config",
            "schema": "#/components/schemas/RowsStale"
          },
          "day.changed": {
            "option": "day.changed",
            "schema": "#/components/schemas/DayChanged"
          },
          "decks.counts": {
            "resource": "decks",
            "schema": "#/components/schemas/DecksCounts"
          },
          "decks.stale": {
            "resource": "decks",
            "schema": "#/components/schemas/RowsStale"
          },
          "gap": {
            "schema": "#/components/schemas/Gap"
          },
          "note_types.stale": {
            "resource": "note_types",
            "schema": "#/components/schemas/RowsStale"
          },
          "notes.created": {
            "resource": "notes",
            "schema": "#/components/schemas/RowsChanged"
          },
          "notes.deleted": {
            "resource": "notes",
            "schema": "#/components/schemas/RowsChanged"
          },
          "notes.stale": {
            "resource": "notes",
            "schema": "#/components/schemas/RowsStale"
          },
          "notes.updated": {
            "resource": "notes",
            "schema": "#/components/schemas/RowsChanged"
          },
          "ready": {
            "schema": "#/components/schemas/Ready"
          },
          "reviews.created": {
            "resource": "reviews",
            "schema": "#/components/schemas/RowsChanged"
          },
          "reviews.stale": {
            "resource": "reviews",
            "schema": "#/components/schemas/RowsStale"
          },
          "scheduler.stale": {
            "resource": "scheduler",
            "schema": "#/components/schemas/RowsStale"
          },
          "sync": {
            "option": "sync",
            "schema": "#/components/schemas/SyncEvent"
          },
          "tags.stale": {
            "resource": "tags",
            "schema": "#/components/schemas/RowsStale"
          }
        },
        "x-permission": "public"
      }
    },
    "/v1/fsrs:compute-params": {
      "post": {
        "description": "Starts optimizing FSRS parameters from the review history matching `search` (empty = whole collection) and returns a job id to poll. Result: `{params, fsrs_items, health_check_passed}`. With too little history the job finishes with empty params and `fsrs_items` 0.",
        "operationId": "fsrsComputeParams",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ComputeParamsRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobSubmitted"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Optimize FSRS parameters",
        "tags": [
          "FSRS"
        ],
        "x-permission": "read:reviews"
      }
    },
    "/v1/fsrs:evaluate-params": {
      "post": {
        "description": "Starts evaluating the given parameters against the review history matching `search`; poll the job for `{log_loss, rmse_bins}`.",
        "operationId": "fsrsEvaluateParams",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EvaluateParamsRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobSubmitted"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Evaluate FSRS parameters",
        "tags": [
          "FSRS"
        ],
        "x-permission": "read:reviews"
      }
    },
    "/v1/fsrs:optimal-retention": {
      "post": {
        "description": "The desired retention minimizing total workload for this setup. Fields pass through to Anki's simulator verbatim; give real limits (deck_size, new_limit, review_limit, days_to_simulate) or Anki refuses with 'no cards to simulate'. Empty `params` means Anki's built-in FSRS defaults.",
        "operationId": "fsrsOptimalRetention",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SimulateRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OptimalRetentionResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Compute optimal retention",
        "tags": [
          "FSRS"
        ],
        "x-permission": "read:reviews"
      }
    },
    "/v1/fsrs:simulate": {
      "post": {
        "description": "Per-day review/new counts, time cost and knowledge acquisition for the simulated schedule. Fields pass through to Anki's simulator verbatim; give real limits (deck_size, new_limit, review_limit, days_to_simulate) or Anki refuses with 'no cards to simulate'. Empty `params` means Anki's built-in FSRS defaults.",
        "operationId": "fsrsSimulate",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SimulateRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SimulateResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Simulate a review workload",
        "tags": [
          "FSRS"
        ],
        "x-permission": "read:reviews"
      }
    },
    "/v1/fsrs:simulate-workload": {
      "post": {
        "description": "Cost, memorized counts and review counts keyed by desired-retention percent. Fields pass through to Anki's simulator verbatim; give real limits (deck_size, new_limit, review_limit, days_to_simulate) or Anki refuses with 'no cards to simulate'. Empty `params` means Anki's built-in FSRS defaults.",
        "operationId": "fsrsSimulateWorkload",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SimulateRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkloadResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Compare workload across retentions",
        "tags": [
          "FSRS"
        ],
        "x-permission": "read:reviews"
      }
    },
    "/v1/gui/current-card": {
      "get": {
        "description": "`card` is null and `review_active` false when no review is in progress.",
        "operationId": "guiCurrentCard",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CurrentCardResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "The card being reviewed",
        "tags": [
          "GUI"
        ],
        "x-permission": "gui"
      }
    },
    "/v1/gui/selected-notes": {
      "get": {
        "description": "Empty when no Browser is open.",
        "operationId": "guiSelectedNotes",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NoteIdList"
                }
              }
            },
            "description": "Successful Response"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Notes selected in the Browser",
        "tags": [
          "GUI"
        ],
        "x-permission": "gui"
      }
    },
    "/v1/gui:add-cards": {
      "post": {
        "description": "Opens the Add Cards dialog, prefilled when a note is given: its deck and note type (each by name or id) and any fields, tags and files (audio, video, picture, as POST /v1/notes takes them; each is stored now and referenced in its fields). The note is NOT added - the user still confirms. Returns the id the editor holds.",
        "operationId": "guiAddCards",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddCardsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AddCardsResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Open Add Cards",
        "tags": [
          "GUI"
        ],
        "x-permission": "gui"
      }
    },
    "/v1/gui:answer-card": {
      "post": {
        "description": "Presses an answer button (1-4). False unless the answer is showing.",
        "operationId": "guiAnswerCard",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/tsunagi__shared__schemas__gui__AnswerRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuiResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Answer the current card",
        "tags": [
          "GUI"
        ],
        "x-permission": "write:cards"
      }
    },
    "/v1/gui:browse": {
      "post": {
        "description": "Opens the card Browser, optionally running a search and sorting it. Returns the card ids the query matches.",
        "operationId": "guiBrowse",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BrowseRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BrowseResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Open the Browser",
        "tags": [
          "GUI"
        ],
        "x-permission": "gui"
      }
    },
    "/v1/gui:deck-browser": {
      "post": {
        "description": "Returns Anki to the deck list.",
        "operationId": "guiDeckBrowser",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuiResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Show the deck list",
        "tags": [
          "GUI"
        ],
        "x-permission": "gui"
      }
    },
    "/v1/gui:deck-overview": {
      "post": {
        "description": "Selects a deck, by name or id, and shows its overview screen. 404 when there is no such deck.",
        "operationId": "guiDeckOverview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeckRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuiResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Show a deck's overview",
        "tags": [
          "GUI"
        ],
        "x-permission": "gui"
      }
    },
    "/v1/gui:deck-review": {
      "post": {
        "description": "Selects a deck, by name or id, and enters the reviewer. 404 when there is no such deck.",
        "operationId": "guiDeckReview",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeckRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuiResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Start reviewing a deck",
        "tags": [
          "GUI"
        ],
        "x-permission": "gui"
      }
    },
    "/v1/gui:edit-note": {
      "post": {
        "description": "Opens the Browser focused on one note. Deviation from AnkiConnect, which opens its own standalone editor dialog.",
        "operationId": "guiEditNote",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NoteIdRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuiResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Open a note for editing",
        "tags": [
          "GUI"
        ],
        "x-permission": "gui"
      }
    },
    "/v1/gui:exit": {
      "post": {
        "description": "Shuts Anki down. Deferred by a second so this reply reaches you first.",
        "operationId": "guiExit",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuiResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Close Anki",
        "tags": [
          "GUI"
        ],
        "x-permission": "manage"
      }
    },
    "/v1/gui:import-file": {
      "post": {
        "description": "Schedules Anki's import UI, on the given file if one is named. The path is resolved on the machine running Anki. Returns ok=true once the UI thread accepts the launch request; this does not confirm that a file was selected or imported. Cancellation and later errors are handled in Anki, and no job ID is created.",
        "operationId": "guiImportFile",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImportFileRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuiResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Request the import dialog",
        "tags": [
          "GUI"
        ],
        "x-permission": "gui"
      }
    },
    "/v1/gui:play-audio": {
      "post": {
        "description": "Replays audio on the card being reviewed.",
        "operationId": "guiPlayAudio",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuiResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Replay the card's audio",
        "tags": [
          "GUI"
        ],
        "x-permission": "gui"
      }
    },
    "/v1/gui:select-card": {
      "post": {
        "description": "Selects one card in an already-open Browser. False when none is open (this does not open one) or its search doesn't show the card; 404 for a card that doesn't exist.",
        "operationId": "guiSelectCard",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CardIdRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuiResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Select a card in the Browser",
        "tags": [
          "GUI"
        ],
        "x-permission": "gui"
      }
    },
    "/v1/gui:set-add-note-data": {
      "post": {
        "description": "Sets deck, note type (each by name or id), fields or tags on an already-open Add Cards dialog. False when it isn't open.",
        "operationId": "guiSetAddNoteData",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetAddNoteDataRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuiResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Amend the open Add Cards dialog",
        "tags": [
          "GUI"
        ],
        "x-permission": "gui"
      }
    },
    "/v1/gui:show-answer": {
      "post": {
        "description": "Reveals the answer side. False when no review is in progress.",
        "operationId": "guiShowAnswer",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuiResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Show the answer",
        "tags": [
          "GUI"
        ],
        "x-permission": "gui"
      }
    },
    "/v1/gui:show-question": {
      "post": {
        "description": "Re-displays the question side. False when no review is in progress.",
        "operationId": "guiShowQuestion",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuiResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Show the question",
        "tags": [
          "GUI"
        ],
        "x-permission": "gui"
      }
    },
    "/v1/gui:start-card-timer": {
      "post": {
        "description": "Resets how long the current card is recorded as having taken.",
        "operationId": "guiStartCardTimer",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuiResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Restart the answer timer",
        "tags": [
          "GUI"
        ],
        "x-permission": "gui"
      }
    },
    "/v1/gui:undo": {
      "post": {
        "description": "Undoes the last step, as Ctrl+Z would, and names it; `undone` is null when there is nothing to undo.",
        "operationId": "guiUndo",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UndoResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Undo",
        "tags": [
          "GUI"
        ],
        "x-permission": "write"
      }
    },
    "/v1/health": {
      "get": {
        "description": "Verify the Tsunagi server is running and responsive. Public; `caller` says who the request counts as, so sending your key checks it.",
        "operationId": "checkHealth",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            },
            "description": "Successful Response"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "summary": "Check API health",
        "tags": [
          "Health"
        ],
        "x-permission": "public"
      }
    },
    "/v1/jobs/{job_id}": {
      "get": {
        "description": "Status, best-effort progress while running, and the result or error once terminal. Finished jobs are kept in memory until evicted, not persisted. Import jobs and add-on actions report their result or error but do not expose progress or support abort.",
        "operationId": "getJob",
        "parameters": [
          {
            "$ref": "#/components/parameters/path.job_id"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobInfo"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Poll a job",
        "tags": [
          "Jobs"
        ],
        "x-permission": "read:collection"
      }
    },
    "/v1/jobs/{job_id}:abort": {
      "post": {
        "description": "A 200 here guarantees the job ends `aborted` - poll to observe it. Anki is asked to stop the computation; if it finishes anyway (its abort flag has blind spots), the result is discarded - these computations write nothing, so nothing is lost but the numbers. 409 if the job already ended or is an import job or add-on action, which cannot be aborted.",
        "operationId": "abortJob",
        "parameters": [
          {
            "$ref": "#/components/parameters/path.job_id"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobInfo"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Abort a job",
        "tags": [
          "Jobs"
        ],
        "x-permission": "read:collection"
      }
    },
    "/v1/media": {
      "get": {
        "description": "Query media with filtering, field selection, and pagination. Every file in the media folder: filename, size and mtime (where=filename$=\".mp3\").",
        "operationId": "listMedia",
        "parameters": [
          {
            "$ref": "#/components/parameters/query.select"
          },
          {
            "$ref": "#/components/parameters/query.where"
          },
          {
            "description": "Sort: a name, optionally with :asc (default) or :desc, e.g. due:desc. Cards and notes use Anki's Browser sorts (due, interval, ease, lapses, reviews, created, card_modified, note_modified, deck, note_type, sort_field, tags, position, card_type; difficulty, stability and retrievability on cards), and the row fields that sort the same (reps, modified; id on notes); other resources a field of their rows. Ties in ascending id. Without it, rows come in ascending id.",
            "in": "query",
            "name": "order",
            "required": false,
            "schema": {
              "type": "string",
              "x-sorts": [
                "filename",
                "modified",
                "size"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/query.distinct_on"
          },
          {
            "$ref": "#/components/parameters/query.shape"
          },
          {
            "$ref": "#/components/parameters/query.include"
          },
          {
            "$ref": "#/components/parameters/query.limit"
          },
          {
            "$ref": "#/components/parameters/query.cursor"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Paginated_MediaFileRow_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "List media",
        "tags": [
          "Media"
        ],
        "x-permission": "read:media"
      },
      "post": {
        "description": "Accepts one upload object or an array. Each input provides exactly one of base64 'data', a server-local 'path' (disabled by default), or a 'url'. Always returns created and failed arrays with zero-based input indexes. Successful files stay stored when another is rejected. Anki renames on collision; use the returned filename. The configured upload-size limit applies per file. Media storage is not undoable.",
        "operationId": "storeMedia",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "anyOf": [
                  {
                    "items": {
                      "$ref": "#/components/schemas/MediaUpload"
                    },
                    "type": "array"
                  },
                  {
                    "$ref": "#/components/schemas/MediaUpload"
                  }
                ],
                "description": "One upload or an array of uploads"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaCreateResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Store one or more media files",
        "tags": [
          "Media"
        ],
        "x-permission": "write:media"
      }
    },
    "/v1/media/query": {
      "post": {
        "description": "Query media using request body. Supports complex queries with filtering and field selection. Omit cursor or use null to start at page one; malformed or empty cursors return 400.",
        "operationId": "queryMedia",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/QueryRequest"
                  }
                ],
                "description": "Query parameters in request body"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Paginated_MediaFileRow_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Query media (POST)",
        "tags": [
          "Media"
        ],
        "x-permission": "read:media"
      }
    },
    "/v1/media/{filename}": {
      "delete": {
        "description": "Moves the file to Anki's media trash.",
        "operationId": "deleteMediaFile",
        "parameters": [
          {
            "$ref": "#/components/parameters/path.filename"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MediaDeletionResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Delete a media file",
        "tags": [
          "Media"
        ],
        "x-permission": "write:media"
      },
      "get": {
        "description": "Streams the raw bytes with a guessed Content-Type. Note: when a request needs a key this URL cannot be used directly in <img src> - browsers can't attach the header; fetch() it and use createObjectURL.",
        "operationId": "getMediaFile",
        "parameters": [
          {
            "$ref": "#/components/parameters/path.filename"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Download a media file",
        "tags": [
          "Media"
        ],
        "x-permission": "read:media"
      }
    },
    "/v1/note-types": {
      "get": {
        "description": "Query note types with filtering, field selection, and pagination. Note types define the structure of cards in Anki. Select note_count for the live number of notes using each type across all decks, or filter with where=note_count>0. Counts are included in full native query results; name/ID-only queries do not calculate them.",
        "operationId": "listNoteTypes",
        "parameters": [
          {
            "$ref": "#/components/parameters/query.select"
          },
          {
            "$ref": "#/components/parameters/query.where"
          },
          {
            "description": "Sort: a name, optionally with :asc (default) or :desc, e.g. due:desc. Cards and notes use Anki's Browser sorts (due, interval, ease, lapses, reviews, created, card_modified, note_modified, deck, note_type, sort_field, tags, position, card_type; difficulty, stability and retrievability on cards), and the row fields that sort the same (reps, modified; id on notes); other resources a field of their rows. Ties in ascending id. Without it, rows come in ascending id.",
            "in": "query",
            "name": "order",
            "required": false,
            "schema": {
              "type": "string",
              "x-sorts": [
                "css",
                "id",
                "latex_post",
                "latex_pre",
                "latex_svg",
                "modified",
                "name",
                "note_count",
                "original_id",
                "original_stock_kind",
                "sort_field",
                "type",
                "usn"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/query.distinct_on"
          },
          {
            "$ref": "#/components/parameters/query.shape"
          },
          {
            "$ref": "#/components/parameters/query.include"
          },
          {
            "$ref": "#/components/parameters/query.limit"
          },
          {
            "$ref": "#/components/parameters/query.cursor"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Paginated_NoteTypeRow_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "List note types",
        "tags": [
          "Note Types"
        ],
        "x-permission": "read:note_types"
      },
      "post": {
        "description": "Create a new note type. Note types define the structure of cards in Anki. Select note_count for the live number of notes using each type across all decks, or filter with where=note_count>0. Counts are included in full native query results; name/ID-only queries do not calculate them.",
        "operationId": "createNoteType",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/NoteTypeCreate"
                  }
                ],
                "description": "Note type data to create"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MutationResult_Any_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Create note type",
        "tags": [
          "Note Types"
        ],
        "x-permission": "write:note_types"
      }
    },
    "/v1/note-types/query": {
      "post": {
        "description": "Query note types using request body. Supports complex queries with filtering and field selection. Omit cursor or use null to start at page one; malformed or empty cursors return 400.",
        "operationId": "queryNoteTypes",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/QueryRequest"
                  }
                ],
                "description": "Query parameters in request body"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Paginated_NoteTypeRow_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Query note types (POST)",
        "tags": [
          "Note Types"
        ],
        "x-permission": "read:note_types"
      }
    },
    "/v1/note-types/{id}": {
      "delete": {
        "description": "Delete a note type.",
        "operationId": "deleteNoteType",
        "parameters": [
          {
            "description": "Note type ID to delete",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeletionResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Delete note type",
        "tags": [
          "Note Types"
        ],
        "x-permission": "write:note_types"
      },
      "patch": {
        "description": "Partially update a note type. Only provided fields will be updated.",
        "operationId": "updateNoteType",
        "parameters": [
          {
            "description": "Note type ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/NoteTypePatch"
                  }
                ],
                "description": "Fields to update"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MutationResult_Any_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Update note type",
        "tags": [
          "Note Types"
        ],
        "x-permission": "write:note_types"
      }
    },
    "/v1/note-types/{note_type_id}/fields": {
      "post": {
        "description": "Add a new field to the note type",
        "operationId": "createField",
        "parameters": [
          {
            "$ref": "#/components/parameters/path.note_type_id"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/FieldCreate"
                  }
                ],
                "description": "Subresource data"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MutationResult_Any_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Create field",
        "tags": [
          "Note Types"
        ],
        "x-permission": "write:note_types"
      }
    },
    "/v1/note-types/{note_type_id}/fields/{field_id}": {
      "delete": {
        "description": "Remove a field from the note type",
        "operationId": "deleteField",
        "parameters": [
          {
            "$ref": "#/components/parameters/path.note_type_id"
          },
          {
            "$ref": "#/components/parameters/path.field_id"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MutationResult_Any_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Delete field",
        "tags": [
          "Note Types"
        ],
        "x-permission": "write:note_types"
      },
      "patch": {
        "description": "Update properties of a field in the note type",
        "operationId": "updateField",
        "parameters": [
          {
            "$ref": "#/components/parameters/path.note_type_id"
          },
          {
            "$ref": "#/components/parameters/path.field_id"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/FieldPatch"
                  }
                ],
                "description": "Fields to update"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MutationResult_Any_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Update field",
        "tags": [
          "Note Types"
        ],
        "x-permission": "write:note_types"
      }
    },
    "/v1/note-types/{note_type_id}/fields:order": {
      "put": {
        "description": "Change the order of fields in the note type",
        "operationId": "reorderFields",
        "parameters": [
          {
            "$ref": "#/components/parameters/path.note_type_id"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "additionalProperties": {
                  "items": {},
                  "type": "array"
                },
                "description": "Order specification",
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MutationResult_Any_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Reorder fields",
        "tags": [
          "Note Types"
        ],
        "x-permission": "write:note_types"
      }
    },
    "/v1/note-types/{note_type_id}/templates": {
      "post": {
        "description": "Add a new template to the note type",
        "operationId": "createTemplate",
        "parameters": [
          {
            "$ref": "#/components/parameters/path.note_type_id"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/TemplateCreate"
                  }
                ],
                "description": "Subresource data"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MutationResult_Any_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Create template",
        "tags": [
          "Note Types"
        ],
        "x-permission": "write:note_types"
      }
    },
    "/v1/note-types/{note_type_id}/templates/{template_id}": {
      "delete": {
        "description": "Remove a template from the note type",
        "operationId": "deleteTemplate",
        "parameters": [
          {
            "$ref": "#/components/parameters/path.note_type_id"
          },
          {
            "$ref": "#/components/parameters/path.template_id"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MutationResult_Any_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Delete template",
        "tags": [
          "Note Types"
        ],
        "x-permission": "write:note_types"
      },
      "patch": {
        "description": "Update properties of a template in the note type",
        "operationId": "updateTemplate",
        "parameters": [
          {
            "$ref": "#/components/parameters/path.note_type_id"
          },
          {
            "$ref": "#/components/parameters/path.template_id"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/TemplatePatch"
                  }
                ],
                "description": "Fields to update"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MutationResult_Any_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Update template",
        "tags": [
          "Note Types"
        ],
        "x-permission": "write:note_types"
      }
    },
    "/v1/note-types/{note_type_id}/templates:order": {
      "put": {
        "description": "Change the order of templates in the note type",
        "operationId": "reorderTemplates",
        "parameters": [
          {
            "$ref": "#/components/parameters/path.note_type_id"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "additionalProperties": {
                  "items": {},
                  "type": "array"
                },
                "description": "Order specification",
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MutationResult_Any_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Reorder templates",
        "tags": [
          "Note Types"
        ],
        "x-permission": "write:note_types"
      }
    },
    "/v1/note-types:find-replace": {
      "post": {
        "description": "Literal (not regex) replacement in card templates and CSS. Name one note type by `note_type_id` or `note_type_name`, or omit both to sweep every note type. Only note types that actually contained the text are touched or counted.",
        "operationId": "noteTypesFindReplace",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FindReplaceRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FindReplaceResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Find and replace across templates and styling",
        "tags": [
          "Note Types"
        ],
        "x-permission": "write:notes"
      }
    },
    "/v1/notes": {
      "get": {
        "description": "Query notes with filtering, field selection, and pagination. Notes hold the content; cards are generated from them by a note type's templates. Use `search` for Anki query syntax.",
        "operationId": "listNotes",
        "parameters": [
          {
            "$ref": "#/components/parameters/query.select"
          },
          {
            "$ref": "#/components/parameters/query.where"
          },
          {
            "description": "Anki search string (e.g. 'deck:Japanese tag:verb').",
            "in": "query",
            "name": "search",
            "required": false,
            "schema": {
              "type": "string",
              "x-from": [
                "cards",
                "decks",
                "note_types",
                "tags"
              ]
            }
          },
          {
            "description": "Sort: a name, optionally with :asc (default) or :desc, e.g. due:desc. Cards and notes use Anki's Browser sorts (due, interval, ease, lapses, reviews, created, card_modified, note_modified, deck, note_type, sort_field, tags, position, card_type; difficulty, stability and retrievability on cards), and the row fields that sort the same (reps, modified; id on notes); other resources a field of their rows. Ties in ascending id. Without it, rows come in ascending id.",
            "in": "query",
            "name": "order",
            "required": false,
            "schema": {
              "type": "string",
              "x-sorts": [
                "card_modified",
                "card_type",
                "created",
                "deck",
                "due",
                "ease",
                "id",
                "interval",
                "lapses",
                "modified",
                "note_modified",
                "note_type",
                "position",
                "reviews",
                "sort_field",
                "tags"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/query.distinct_on"
          },
          {
            "$ref": "#/components/parameters/query.shape"
          },
          {
            "$ref": "#/components/parameters/query.include"
          },
          {
            "$ref": "#/components/parameters/query.limit"
          },
          {
            "$ref": "#/components/parameters/query.cursor"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Paginated_NoteRow_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "List notes",
        "tags": [
          "Notes"
        ],
        "x-permission": "read:notes"
      },
      "post": {
        "description": "Accepts one note object or an array. Always returns created and failed arrays, with zero-based input indexes (0 for a single object). Valid notes stay saved when another note is rejected. Inputs are processed in order and successful additions form one undo step. Malformed request bodies return 422 before writes. include adds parts, comma-separated: cards for each note's card IDs, duplicate_ids for the existing notes each duplicate matches (as POST /v1/notes:check reports them). Files can come with each note in audio, video and picture, as in AnkiConnect: each is stored once its note passes its checks, and its reference is appended to the listed fields. For many or large files, or one file shared by several notes, upload them to /v1/media first instead.",
        "operationId": "createNotes",
        "parameters": [
          {
            "description": "Extra parts, comma-separated: cards (each note's card IDs), duplicate_ids (the notes each duplicate matches).",
            "in": "query",
            "name": "include",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "anyOf": [
                  {
                    "items": {
                      "$ref": "#/components/schemas/NoteCreate"
                    },
                    "type": "array"
                  },
                  {
                    "$ref": "#/components/schemas/NoteCreate"
                  }
                ],
                "description": "One note or an array of notes"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NoteCreateResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Create one or more notes",
        "tags": [
          "Notes"
        ],
        "x-permission": "write:notes"
      }
    },
    "/v1/notes/query": {
      "post": {
        "description": "Query notes using request body. Supports complex queries with filtering and field selection. Omit cursor or use null to start at page one; malformed or empty cursors return 400.",
        "operationId": "queryNotes",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/QueryRequest"
                  }
                ],
                "description": "Query parameters in request body"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Paginated_NoteRow_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Query notes (POST)",
        "tags": [
          "Notes"
        ],
        "x-permission": "read:notes"
      }
    },
    "/v1/notes/{id}": {
      "delete": {
        "description": "Delete a note.",
        "operationId": "deleteNote",
        "parameters": [
          {
            "description": "Note ID to delete",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeletionResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Delete note",
        "tags": [
          "Notes"
        ],
        "x-permission": "write:notes"
      },
      "patch": {
        "description": "Partially update a note. Only provided fields will be updated.",
        "operationId": "updateNote",
        "parameters": [
          {
            "description": "Note ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/NotePatch"
                  }
                ],
                "description": "Fields to update"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MutationResult_Any_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Update note",
        "tags": [
          "Notes"
        ],
        "x-permission": "write:notes"
      }
    },
    "/v1/notes:change-note-type": {
      "post": {
        "description": "Anki's Change Note Type, for notes all of one note type: each new field takes an old field's content, and each new card template an old template's cards with their reviews. `fields` and `templates` name the maps; Anki's defaults fill the rest. Cards no new template takes are deleted, and Anki adds the cards a new template makes. One undo step; like editing a note type's fields, it needs a full sync next. With `dry_run`, answers the maps, the fields whose content would be lost and the cards that would be removed, and changes nothing. A missing note is skipped; notes of two note types are a 409; a change that changes nothing writes nothing.",
        "operationId": "notesChangeNoteType",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NoteTypeChange"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NoteTypeChangeResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Change notes' note type",
        "tags": [
          "Notes"
        ],
        "x-permission": "write:notes"
      }
    },
    "/v1/notes:check": {
      "post": {
        "description": "Takes one note or an array, as POST /v1/notes does, and reports for each whether it can be added, and why not (empty first field, duplicate, unknown note type/deck). Adds nothing. include=duplicate_ids also looks up the notes each duplicate matches (duplicate_note_ids; null without it).",
        "operationId": "checkNotes",
        "parameters": [
          {
            "description": "duplicate_ids: also look up the notes each duplicate matches.",
            "in": "query",
            "name": "include",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "anyOf": [
                  {
                    "items": {
                      "$ref": "#/components/schemas/NoteCreate"
                    },
                    "type": "array"
                  },
                  {
                    "$ref": "#/components/schemas/NoteCreate"
                  }
                ],
                "description": "One note or an array of notes"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NoteCheckResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Check whether notes can be added",
        "tags": [
          "Notes"
        ],
        "x-permission": "read:notes"
      }
    },
    "/v1/notes:delete": {
      "post": {
        "description": "Deletes the notes and their cards, as one undo step. A missing note is skipped; `affected` counts the notes deleted. Same `resource:verb` form as the card verbs.",
        "operationId": "notesDelete",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NoteIds"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerbResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Delete notes",
        "tags": [
          "Notes"
        ],
        "x-permission": "write:notes"
      }
    },
    "/v1/notes:upsert": {
      "post": {
        "description": "Accepts one note object or an array, like POST /v1/notes. Each note is matched against existing notes of its note type: by default with Anki's duplicate check (the first field, HTML ignored, honoring duplicateScope), or by `match.field`'s exact content. No match: the note is created (duplicate rules apply). One match: that note is updated by `on_match`, and its cards stay in their decks. Several matches: the item fails as `ambiguous` with the note ids. Fields the request does not send are never touched; a field without a rule is filled only if empty. Returns created, updated (with the fields that changed) and failed; successful writes form one undo step.",
        "operationId": "upsertNotes",
        "parameters": [
          {
            "description": "cards: also return card IDs.",
            "in": "query",
            "name": "include",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "anyOf": [
                  {
                    "items": {
                      "$ref": "#/components/schemas/NoteUpsert"
                    },
                    "type": "array"
                  },
                  {
                    "$ref": "#/components/schemas/NoteUpsert"
                  }
                ],
                "description": "One note or an array of notes"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NoteUpsertResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Create notes, or update the notes they match",
        "tags": [
          "Notes"
        ],
        "x-permission": "write:notes"
      }
    },
    "/v1/permissions:request": {
      "post": {
        "description": "For a website or a program that has no key yet. Anki asks you whether to allow it and which role it gets; the answer comes when you choose, or as denied after two minutes. Allowed, it becomes an app in Apps & keys and the answer carries its key, this once: send it as X-Api-Key from then on. A website is told apart by its Origin, a program by the `name` it sends (Anki shows that it can't be checked). Asking again always asks you, and allowing gives the app a new key; the old one stops working. A website or program you asked Anki to ignore is denied without asking. Needs no key and no permission. AnkiConnect's requestPermission gives no key.",
        "operationId": "requestPermission",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AccessRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PermissionRequestResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "summary": "Ask for access",
        "tags": [
          "Collection"
        ],
        "x-permission": "public"
      }
    },
    "/v1/profiles": {
      "get": {
        "description": "Every Anki profile, and which one is currently open.",
        "operationId": "listProfiles",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileList"
                }
              }
            },
            "description": "Successful Response"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "List profiles",
        "tags": [
          "Collection"
        ],
        "x-permission": "read:collection"
      }
    },
    "/v1/profiles:load": {
      "post": {
        "description": "Schedules closing the current profile and opening another. `loaded` confirms acceptance, not completion; an unknown name is a 404. The server restarts during the switch; requests may encounter 503 or a connection interruption. Reconnect and GET /v1/profiles to confirm the active profile.",
        "operationId": "loadProfile",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProfileLoad"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileLoadResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Switch profile",
        "tags": [
          "Collection"
        ],
        "x-permission": "manage"
      }
    },
    "/v1/review-days": {
      "get": {
        "description": "Query review days with filtering, field selection, and pagination. One row per Anki day with reviews: what a heatmap needs. `search` counts only the reviews of the cards an Anki search finds (a deck, a tag...); without it, the whole collection. include=total is the number of days studied.",
        "operationId": "listReviewDays",
        "parameters": [
          {
            "$ref": "#/components/parameters/query.select"
          },
          {
            "$ref": "#/components/parameters/query.where"
          },
          {
            "description": "Anki search string (e.g. 'deck:Japanese tag:verb').",
            "in": "query",
            "name": "search",
            "required": false,
            "schema": {
              "type": "string",
              "x-from": []
            }
          },
          {
            "description": "Sort: a name, optionally with :asc (default) or :desc, e.g. due:desc. Cards and notes use Anki's Browser sorts (due, interval, ease, lapses, reviews, created, card_modified, note_modified, deck, note_type, sort_field, tags, position, card_type; difficulty, stability and retrievability on cards), and the row fields that sort the same (reps, modified; id on notes); other resources a field of their rows. Ties in ascending id. Without it, rows come in ascending id.",
            "in": "query",
            "name": "order",
            "required": false,
            "schema": {
              "type": "string",
              "x-sorts": [
                "date",
                "duration_ms",
                "filtered",
                "learn",
                "mature",
                "relearn",
                "reviews",
                "young"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/query.distinct_on"
          },
          {
            "$ref": "#/components/parameters/query.shape"
          },
          {
            "$ref": "#/components/parameters/query.include"
          },
          {
            "$ref": "#/components/parameters/query.limit"
          },
          {
            "$ref": "#/components/parameters/query.cursor"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Paginated_ReviewDayRow_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "List review days",
        "tags": [
          "Statistics"
        ],
        "x-permission": "read:reviews"
      }
    },
    "/v1/review-days/query": {
      "post": {
        "description": "Query review days using request body. Supports complex queries with filtering and field selection. Omit cursor or use null to start at page one; malformed or empty cursors return 400.",
        "operationId": "queryReviewDays",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/QueryRequest"
                  }
                ],
                "description": "Query parameters in request body"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Paginated_ReviewDayRow_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Query review days (POST)",
        "tags": [
          "Statistics"
        ],
        "x-permission": "read:reviews"
      }
    },
    "/v1/reviews": {
      "get": {
        "description": "Query reviews with filtering, field selection, and pagination. Review history from the revlog, newest last. `id` is the review's epoch-ms timestamp and its identity, so pages come back in review order. `search` takes Anki query syntax and means \"reviews of the cards this matches\" - the revlog itself is not searchable. Reviews are recorded by the scheduler; POST inserts raw rows for history imports.",
        "operationId": "listReviews",
        "parameters": [
          {
            "$ref": "#/components/parameters/query.select"
          },
          {
            "$ref": "#/components/parameters/query.where"
          },
          {
            "description": "Anki search string (e.g. 'deck:Japanese tag:verb').",
            "in": "query",
            "name": "search",
            "required": false,
            "schema": {
              "type": "string",
              "x-from": [
                "cards",
                "notes",
                "decks",
                "note_types",
                "tags"
              ]
            }
          },
          {
            "description": "Sort: a name, optionally with :asc (default) or :desc, e.g. due:desc. Cards and notes use Anki's Browser sorts (due, interval, ease, lapses, reviews, created, card_modified, note_modified, deck, note_type, sort_field, tags, position, card_type; difficulty, stability and retrievability on cards), and the row fields that sort the same (reps, modified; id on notes); other resources a field of their rows. Ties in ascending id. Without it, rows come in ascending id.",
            "in": "query",
            "name": "order",
            "required": false,
            "schema": {
              "type": "string",
              "x-sorts": [
                "id",
                "card_id",
                "usn",
                "rating",
                "interval",
                "last_interval",
                "ease_factor",
                "duration_ms",
                "type"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/query.distinct_on"
          },
          {
            "$ref": "#/components/parameters/query.shape"
          },
          {
            "$ref": "#/components/parameters/query.include"
          },
          {
            "$ref": "#/components/parameters/query.limit"
          },
          {
            "$ref": "#/components/parameters/query.cursor"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Paginated_ReviewRow_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "List reviews",
        "tags": [
          "Reviews"
        ],
        "x-permission": "read:reviews"
      },
      "post": {
        "description": "Appends rows to the revlog behind the scheduler's back - AnkiConnect's insertReviews, meant for importing review history. Rows use the same fields GET returns (aliases accepted); `id` is the review's epoch-ms timestamp and must be unique; each card must exist (404 naming the ones that don't). All rows land in one transaction or none do. Side effects inherent to a raw revlog write: the undo history and cached study queues are discarded, and no event is emitted.",
        "operationId": "createReviews",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InsertReviewsRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InsertReviewsResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Insert raw review rows",
        "tags": [
          "Reviews"
        ],
        "x-permission": "write:reviews"
      }
    },
    "/v1/reviews/query": {
      "post": {
        "description": "Query reviews using request body. Supports complex queries with filtering and field selection. Omit cursor or use null to start at page one; malformed or empty cursors return 400.",
        "operationId": "queryReviews",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/QueryRequest"
                  }
                ],
                "description": "Query parameters in request body"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Paginated_ReviewRow_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Query reviews (POST)",
        "tags": [
          "Reviews"
        ],
        "x-permission": "read:reviews"
      }
    },
    "/v1/statistics/today": {
      "get": {
        "description": "Anki's day so far, as its Statistics screen's Today section counts it: cards answered, time, correct answers, by kind, and mature cards; and the current streak of days with reviews.",
        "operationId": "getTodayStatistics",
        "parameters": [
          {
            "description": "Anki search: only reviews of the cards it finds count, e.g. `deck:Japanese` or `tag:verbs`. Empty: the whole collection.",
            "in": "query",
            "name": "search",
            "required": false,
            "schema": {
              "default": "",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TodayStatistics"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Today's statistics",
        "tags": [
          "Statistics"
        ],
        "x-permission": "read:reviews"
      }
    },
    "/v1/tags": {
      "get": {
        "description": "Query tags with filtering, field selection, and pagination. Every tag in the collection; nesting uses '::' (where=name^=\"Japanese::\" for one branch).",
        "operationId": "listTags",
        "parameters": [
          {
            "$ref": "#/components/parameters/query.select"
          },
          {
            "$ref": "#/components/parameters/query.where"
          },
          {
            "description": "Sort: a name, optionally with :asc (default) or :desc, e.g. due:desc. Cards and notes use Anki's Browser sorts (due, interval, ease, lapses, reviews, created, card_modified, note_modified, deck, note_type, sort_field, tags, position, card_type; difficulty, stability and retrievability on cards), and the row fields that sort the same (reps, modified; id on notes); other resources a field of their rows. Ties in ascending id. Without it, rows come in ascending id.",
            "in": "query",
            "name": "order",
            "required": false,
            "schema": {
              "type": "string",
              "x-sorts": [
                "name"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/query.distinct_on"
          },
          {
            "$ref": "#/components/parameters/query.shape"
          },
          {
            "$ref": "#/components/parameters/query.include"
          },
          {
            "$ref": "#/components/parameters/query.limit"
          },
          {
            "$ref": "#/components/parameters/query.cursor"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Paginated_TagRow_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "List tags",
        "tags": [
          "Tags"
        ],
        "x-permission": "read:tags"
      }
    },
    "/v1/tags/query": {
      "post": {
        "description": "Query tags using request body. Supports complex queries with filtering and field selection. Omit cursor or use null to start at page one; malformed or empty cursors return 400.",
        "operationId": "queryTags",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/QueryRequest"
                  }
                ],
                "description": "Query parameters in request body"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Paginated_TagRow_"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Query tags (POST)",
        "tags": [
          "Tags"
        ],
        "x-permission": "read:tags"
      }
    },
    "/v1/tags/{tag}": {
      "delete": {
        "description": "Removes the tag and its children from every note.",
        "operationId": "deleteTag",
        "parameters": [
          {
            "description": "The tag to remove",
            "in": "path",
            "name": "tag",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TagMutationResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Delete a tag",
        "tags": [
          "Tags"
        ],
        "x-permission": "write:tags"
      },
      "patch": {
        "description": "Renames the tag and its children ('a' also renames 'a::b').",
        "operationId": "renameTag",
        "parameters": [
          {
            "description": "The tag to rename",
            "in": "path",
            "name": "tag",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TagRename"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TagMutationResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Rename a tag",
        "tags": [
          "Tags"
        ],
        "x-permission": "write:tags"
      }
    },
    "/v1/tags:bulk-add": {
      "post": {
        "description": "One undoable op for the whole batch. `tags` is space-separated.",
        "operationId": "bulkAddTags",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TagBulkRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TagMutationResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Add tags to many notes",
        "tags": [
          "Tags"
        ],
        "x-permission": "write:tags"
      }
    },
    "/v1/tags:bulk-remove": {
      "post": {
        "description": "One undoable op for the whole batch. `tags` is space-separated.",
        "operationId": "bulkRemoveTags",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TagBulkRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TagMutationResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Remove tags from many notes",
        "tags": [
          "Tags"
        ],
        "x-permission": "write:tags"
      }
    },
    "/v1/tags:clear-unused": {
      "post": {
        "description": "Drops registered tags that no note references any more.",
        "operationId": "clearUnusedTags",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TagMutationResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "ApiKey": []
          },
          {
            "BearerAuth": []
          }
        ],
        "summary": "Clear unused tags",
        "tags": [
          "Tags"
        ],
        "x-permission": "write:tags"
      }
    }
  },
  "tags": [
    {
      "description": "Note types and their fields and templates. Note types define the structure of cards in Anki.",
      "name": "Note Types"
    },
    {
      "description": "Deck hierarchy (nested names use '::'). Filtered (dynamic) decks appear in reads; mutations operate on normal decks.",
      "name": "Decks"
    },
    {
      "description": "Notes hold the content; cards are generated from them. Supports Anki search syntax via the `search` parameter.",
      "name": "Notes"
    },
    {
      "description": "Cards generated from notes by a note type's templates. Reads support Anki search syntax; scheduling changes are batch verb routes (POST /v1/cards:suspend and friends).",
      "name": "Cards"
    },
    {
      "description": "Tags across the collection. Nesting uses '::', and operations apply to a tag and its children together, like Anki's own.",
      "name": "Tags"
    },
    {
      "description": "Deck options presets (scheduling limits and intervals). Returned as Anki's config dicts verbatim so newer keys survive a round trip.",
      "name": "Deck Presets"
    },
    {
      "description": "Files in the collection's media folder. Downloads stream raw bytes; uploads report the name Anki actually stored.",
      "name": "Media"
    },
    {
      "description": "FSRS parameter optimization, evaluation and simulation. Optimize/evaluate run as async jobs (submit, then poll /v1/jobs/{id}); the simulator answers synchronously.",
      "name": "FSRS"
    },
    {
      "description": "Live collection events as Server-Sent Events: operations, reviews, sync, and full-refresh signals.",
      "name": "Events"
    },
    {
      "description": "API health and status checks",
      "name": "Health"
    },
    {
      "description": "AnkiConnect-compatible RPC endpoint for gradual migration",
      "name": "AnkiConnect Compatibility"
    }
  ],
  "servers": [
    {
      "url": "http://127.0.0.1:7777",
      "description": "Tsunagi on this computer"
    }
  ]
}