{
  "openapi": "3.1.0",
  "info": {
    "title": "Lector API",
    "version": "1.0.0",
    "description": "Lector reads and studies text in a target language. This document describes the\nHTTP API. The Lector web client and every personal access token use this same\nAPI.\n\n## Authentication\n\nEach `/api/*` endpoint needs a credential. Two kinds work:\n\n- A personal access token, sent as `Authorization: Bearer <token>`. Create a\n  token in Settings, under API tokens. Scripts and integrations use this kind.\n- A session cookie, which the browser client sends. Lector Cloud only.\n\nEach token carries scopes. Every endpoint below states the scope that it needs.\nSome resources have no scope at all. A token cannot reach those resources, so\nthis document leaves them out. Token management, billing, moderation and the\nadmin console are browser-only surfaces.\n\n## Languages\n\nLector separates your data by language. Most endpoints accept an optional\n`language` query parameter. Give it a language pack code, for example `af`, `es`\nor `grc`. If you omit the parameter, the API uses the active language of the\naccount.\n\n## Errors\n\nAn error response carries a JSON body with an `error` string. A `429` response\nalso names the plan limit that stopped the call.\n\n## Accounts\n\nThe cloud sign-in, sign-up, verification and two-factor endpoints are at\n`/api/auth/*`. Better Auth serves those endpoints and documents its own\ncontract. This document does not describe them.\n",
    "license": {
      "name": "AGPL-3.0-only",
      "identifier": "AGPL-3.0-only"
    }
  },
  "servers": [
    {
      "url": "https://app.lector.dev",
      "description": "Lector Cloud"
    },
    {
      "url": "http://localhost:3457",
      "description": "Self-hosted API (default port)"
    }
  ],
  "tags": [
    {
      "name": "Library",
      "description": "Collections, groups and lessons."
    },
    {
      "name": "Import",
      "description": "Bring text, EPUB, audio and video into the library."
    },
    {
      "name": "Vocabulary",
      "description": "Saved words and phrases, and word knowledge states."
    },
    {
      "name": "Practice",
      "description": "Cloze practice cards and their review schedule."
    },
    {
      "name": "Dictionary",
      "description": "Word lookup and the accepted-translation cache."
    },
    {
      "name": "Language help",
      "description": "Machine translation, explanation and speech."
    },
    {
      "name": "Journal",
      "description": "Written entries and their corrections."
    },
    {
      "name": "Statistics",
      "description": "Daily activity, streaks and fluency estimates."
    },
    {
      "name": "Anki",
      "description": "Card queue and review sync for the Anki add-on."
    },
    {
      "name": "Settings",
      "description": "Account preferences and provider configuration."
    },
    {
      "name": "Data",
      "description": "Export and restore the account’s learning data."
    },
    {
      "name": "Chat",
      "description": "Conversation practice with the language model."
    },
    {
      "name": "Onboarding",
      "description": "Guided first-run state and starter content."
    },
    {
      "name": "Service",
      "description": "Health and deployment information."
    }
  ],
  "security": [
    {
      "PersonalAccessToken": []
    },
    {
      "SessionCookie": []
    }
  ],
  "paths": {
    "/api/anki": {
      "get": {
        "operationId": "getApiAnki",
        "summary": "Check the Anki connection",
        "description": "Reads the deck list through the AnkiConnect add-on. Anki must run on the same host as the API.",
        "tags": [
          "Anki"
        ],
        "responses": {
          "200": {
            "description": "Connection state and decks. An unreachable Anki answers `connected: false`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "connected": {
                      "type": "boolean"
                    },
                    "version": {
                      "type": "integer"
                    },
                    "decks": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "anki:read"
      },
      "post": {
        "operationId": "postApiAnki",
        "summary": "Call an allowed AnkiConnect action",
        "description": "Passes one allowed action to AnkiConnect. The API syncs reviews after it adds a note.",
        "tags": [
          "Anki"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "description": "An allowed AnkiConnect action."
                  },
                  "params": {
                    "type": "object"
                  }
                },
                "required": [
                  "action"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The result from AnkiConnect.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "anki:write"
      }
    },
    "/api/anki/ack": {
      "post": {
        "operationId": "postApiAnkiAck",
        "summary": "Acknowledge created or updated notes",
        "description": "Marks the vocabulary entries as pushed, and clears the pending rows. Echo the `version` from the pull. A card queued again then survives a late acknowledgement.",
        "tags": [
          "Anki"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "results": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "object",
                      "properties": {
                        "vocabId": {
                          "type": "string"
                        },
                        "ankiNoteId": {
                          "type": "integer"
                        },
                        "version": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "vocabId"
                      ]
                    }
                  }
                },
                "required": [
                  "results"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "How many rows the acknowledgement cleared.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "acked": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "anki:write"
      }
    },
    "/api/anki/pending": {
      "get": {
        "operationId": "getApiAnkiPending",
        "summary": "Pull the pending card batch",
        "description": "Returns one batch, and how many rows remain. Loop the pull, the write and the acknowledgement until the queue is empty.",
        "tags": [
          "Anki"
        ],
        "responses": {
          "200": {
            "description": "The batch, and the rows that remain after it.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "pending": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "lectorId": {
                            "type": "string",
                            "description": "The vocabulary entry identifier."
                          },
                          "cardType": {
                            "type": "string",
                            "enum": [
                              "basic",
                              "word",
                              "cloze"
                            ]
                          },
                          "word": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "sentence": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "translation": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "meaning": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "language": {
                            "type": "string"
                          },
                          "sourceUrl": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "clipStartMs": {
                            "type": [
                              "integer",
                              "null"
                            ]
                          },
                          "clipEndMs": {
                            "type": [
                              "integer",
                              "null"
                            ]
                          },
                          "queuedAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "version": {
                            "type": "integer",
                            "description": "Echo it back on the acknowledgement."
                          }
                        }
                      }
                    },
                    "remaining": {
                      "type": "integer",
                      "description": "Advisory count, for progress output."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "anki:read"
      }
    },
    "/api/anki/queue": {
      "post": {
        "operationId": "postApiAnkiQueue",
        "summary": "Queue vocabulary as Anki cards",
        "description": "Adds pending cards for the add-on to pull. A second call for the same entry replaces its pending row.",
        "tags": [
          "Anki"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "items": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "object",
                      "properties": {
                        "vocabId": {
                          "type": "string"
                        },
                        "cardType": {
                          "type": "string",
                          "enum": [
                            "basic",
                            "word",
                            "cloze"
                          ]
                        },
                        "word": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "sentence": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "translation": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "meaning": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "sourceUrl": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Video URL, for a card mined from a transcript."
                        },
                        "clipStartMs": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "clipEndMs": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "vocabId"
                      ]
                    }
                  }
                },
                "required": [
                  "items"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "How many cards the queue took, and how many it refused.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "queued": {
                      "type": "integer"
                    },
                    "failed": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "anki:write"
      }
    },
    "/api/anki/reviews": {
      "post": {
        "operationId": "postApiAnkiReviews",
        "summary": "Report reviews from the add-on",
        "description": "Raises the word state from the Anki review history. The endpoint never lowers a state, and never changes `ignored`.",
        "tags": [
          "Anki"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Send `reviews`, `reviewsByDay`, or an `inventory`.",
                "properties": {
                  "reviews": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string"
                        },
                        "interval": {
                          "type": "number"
                        }
                      }
                    }
                  },
                  "reviewsByDay": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "date": {
                          "type": "string",
                          "description": "Calendar date, `YYYY-MM-DD`."
                        },
                        "count": {
                          "type": "integer"
                        }
                      }
                    }
                  },
                  "inventory": {
                    "type": "object",
                    "description": "The notes the add-on holds."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What the report changed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "anki:write"
      }
    },
    "/api/anki/sync-reviews": {
      "post": {
        "operationId": "postApiAnkiSyncReviews",
        "summary": "Sync the daily review counts from Anki",
        "description": "Reads the review counts through AnkiConnect and stores them, so the heatmap and the streak count Anki days. An unreachable Anki leaves the stored data alone.",
        "tags": [
          "Anki"
        ],
        "responses": {
          "200": {
            "description": "What the sync changed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "anki:write"
      }
    },
    "/api/byok": {
      "delete": {
        "operationId": "deleteApiByok",
        "summary": "Delete your provider key",
        "tags": [
          "Settings"
        ],
        "responses": {
          "200": {
            "description": "The key is deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "enabled": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "settings:write"
      },
      "get": {
        "operationId": "getApiByok",
        "summary": "Get the provider key state",
        "description": "Reports whether this deployment accepts your own provider key, and which key the account holds.",
        "tags": [
          "Settings"
        ],
        "responses": {
          "200": {
            "description": "The state.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "available": {
                      "type": "boolean",
                      "description": "True when the deployment accepts a key."
                    },
                    "enabled": {
                      "type": "boolean",
                      "description": "True when the account holds a key."
                    },
                    "provider": {
                      "type": "string"
                    },
                    "model": {
                      "type": "string"
                    },
                    "providers": {
                      "type": "object",
                      "description": "Each provider, and the models it allows."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "settings:read"
      },
      "put": {
        "operationId": "putApiByok",
        "summary": "Store your own provider key",
        "description": "Validates the key against the provider, then stores it. Send the key only. The API never reads it back.",
        "tags": [
          "Settings"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "provider": {
                    "type": "string",
                    "enum": [
                      "anthropic",
                      "openrouter"
                    ]
                  },
                  "apiKey": {
                    "type": "string",
                    "description": "At most 512 characters."
                  },
                  "model": {
                    "type": "string",
                    "description": "A model the provider catalog lists."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The key is stored.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "enabled": {
                      "type": "boolean"
                    },
                    "provider": {
                      "type": "string"
                    },
                    "model": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "This deployment does not accept your own key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "settings:write"
      }
    },
    "/api/chat": {
      "delete": {
        "operationId": "deleteApiChat",
        "summary": "Clear the conversation history",
        "tags": [
          "Chat"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "The history is deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "chat:write"
      },
      "get": {
        "operationId": "getApiChat",
        "summary": "Get the conversation history",
        "description": "Returns messages oldest first. Page back with `before`.",
        "tags": [
          "Chat"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/LanguageQuery"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum messages. The default is 50.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "before",
            "in": "query",
            "required": false,
            "description": "Return messages created before this time. Use it to page back.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The messages.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string"
                      },
                      "role": {
                        "type": "string",
                        "enum": [
                          "user",
                          "assistant"
                        ]
                      },
                      "content": {
                        "type": "string"
                      },
                      "provider": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "responseId": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "language": {
                        "type": "string"
                      },
                      "createdAt": {
                        "type": "string",
                        "format": "date-time"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "chat:read"
      },
      "post": {
        "operationId": "postApiChat",
        "summary": "Send a chat message",
        "description": "Sends the message to the language model and returns the answer. The Free plan answers without storing the history.",
        "tags": [
          "Chat"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "message": {
                    "type": "string",
                    "description": "At most 32 KiB."
                  },
                  "language": {
                    "type": "string"
                  }
                },
                "required": [
                  "message"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Both messages of the exchange.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "userMessage": {
                      "type": "object"
                    },
                    "assistantMessage": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "chat:write"
      }
    },
    "/api/cloze": {
      "get": {
        "operationId": "getApiCloze",
        "summary": "List practice cards",
        "description": "Cards in the language, soonest review first. Hidden cards stay out.",
        "tags": [
          "Practice"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/LanguageQuery"
          },
          {
            "name": "collection",
            "in": "query",
            "required": false,
            "description": "Return only cards from this bank.",
            "schema": {
              "type": "string",
              "enum": [
                "top500",
                "top1000",
                "top2000",
                "mined",
                "random"
              ]
            }
          },
          {
            "name": "word",
            "in": "query",
            "required": false,
            "description": "Return only cards that blank this exact word.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum cards to return. The default is 100.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching cards.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ClozeCard"
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:read"
      },
      "post": {
        "operationId": "postApiCloze",
        "summary": "Create or replace practice cards",
        "description": "Send one object, or an array for a batch. Every card in a batch must use one language. A card with an existing identifier is replaced.",
        "tags": [
          "Practice"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "object",
                    "properties": {
                      "sentence": {
                        "type": "string"
                      },
                      "clozeWord": {
                        "type": "string"
                      },
                      "clozeIndex": {
                        "type": "integer"
                      },
                      "translation": {
                        "type": "string"
                      },
                      "source": {
                        "type": "string",
                        "enum": [
                          "tatoeba",
                          "mined"
                        ]
                      },
                      "collection": {
                        "type": "string",
                        "enum": [
                          "top500",
                          "top1000",
                          "top2000",
                          "mined",
                          "random"
                        ]
                      },
                      "wordRank": {
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "tatoebaSentenceId": {
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "vocabEntryId": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "masteryLevel": {
                        "type": "integer",
                        "enum": [
                          0,
                          25,
                          50,
                          75,
                          100
                        ]
                      },
                      "nextReview": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "language": {
                        "type": "string"
                      },
                      "id": {
                        "type": "string",
                        "description": "Supply your own identifier. Optional."
                      }
                    },
                    "required": [
                      "sentence",
                      "clozeWord",
                      "translation"
                    ]
                  },
                  {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "sentence": {
                          "type": "string"
                        },
                        "clozeWord": {
                          "type": "string"
                        },
                        "clozeIndex": {
                          "type": "integer"
                        },
                        "translation": {
                          "type": "string"
                        },
                        "source": {
                          "type": "string",
                          "enum": [
                            "tatoeba",
                            "mined"
                          ]
                        },
                        "collection": {
                          "type": "string",
                          "enum": [
                            "top500",
                            "top1000",
                            "top2000",
                            "mined",
                            "random"
                          ]
                        },
                        "wordRank": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "tatoebaSentenceId": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "vocabEntryId": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "masteryLevel": {
                          "type": "integer",
                          "enum": [
                            0,
                            25,
                            50,
                            75,
                            100
                          ]
                        },
                        "nextReview": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "language": {
                          "type": "string"
                        },
                        "id": {
                          "type": "string",
                          "description": "Supply your own identifier. Optional."
                        }
                      },
                      "required": [
                        "sentence",
                        "clozeWord",
                        "translation"
                      ]
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One object answers with the new identifier. An array answers with the stored count.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/CreatedId"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "success": {
                          "type": "boolean"
                        },
                        "count": {
                          "type": "integer"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:write"
      }
    },
    "/api/cloze/{id}": {
      "delete": {
        "operationId": "deleteApiClozeById",
        "summary": "Delete a practice card",
        "tags": [
          "Practice"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Card identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "The card is deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:write"
      },
      "get": {
        "operationId": "getApiClozeById",
        "summary": "Get one practice card",
        "tags": [
          "Practice"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Card identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "The card.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClozeCard"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such record, in this account and language.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:read"
      },
      "put": {
        "operationId": "putApiClozeById",
        "summary": "Update a practice card",
        "tags": [
          "Practice"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Card identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Send only the fields to change.",
                "properties": {
                  "sentence": {
                    "type": "string"
                  },
                  "clozeWord": {
                    "type": "string"
                  },
                  "clozeIndex": {
                    "type": "integer"
                  },
                  "translation": {
                    "type": "string"
                  },
                  "masteryLevel": {
                    "type": "integer",
                    "enum": [
                      0,
                      25,
                      50,
                      75,
                      100
                    ]
                  },
                  "nextReview": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "reviewCount": {
                    "type": "integer"
                  },
                  "lastReviewed": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "timesCorrect": {
                    "type": "integer"
                  },
                  "timesIncorrect": {
                    "type": "integer"
                  },
                  "blacklisted": {
                    "type": "integer",
                    "enum": [
                      0,
                      1
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The card is updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such record, in this account and language.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:write"
      }
    },
    "/api/cloze/{id}/review": {
      "post": {
        "operationId": "postApiClozeByIdReview",
        "summary": "Record a practice answer",
        "description": "The client owns the schedule. Send the new mastery level and the next review time with the answer.",
        "tags": [
          "Practice"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Card identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "correct": {
                    "type": "boolean"
                  },
                  "masteryLevel": {
                    "type": "integer",
                    "enum": [
                      0,
                      25,
                      50,
                      75,
                      100
                    ]
                  },
                  "nextReview": {
                    "type": "string",
                    "format": "date-time"
                  }
                },
                "required": [
                  "correct",
                  "masteryLevel",
                  "nextReview"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The answer is recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such record, in this account and language.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:write"
      }
    },
    "/api/cloze/counts": {
      "get": {
        "operationId": "getApiClozeCounts",
        "summary": "Count cards per bank",
        "tags": [
          "Practice"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "Totals for each bank.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "object",
                    "properties": {
                      "total": {
                        "type": "integer"
                      },
                      "due": {
                        "type": "integer"
                      },
                      "mastered": {
                        "type": "integer"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:read"
      }
    },
    "/api/cloze/due": {
      "get": {
        "operationId": "getApiClozeDue",
        "summary": "Get the cards due for practice",
        "tags": [
          "Practice"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/LanguageQuery"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum cards to return. The default is 20.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "mode",
            "in": "query",
            "required": false,
            "description": "`new` returns cards with no review yet. `review` returns seen cards that are due. Omit it for both.",
            "schema": {
              "type": "string",
              "enum": [
                "new",
                "review"
              ]
            }
          },
          {
            "name": "collection",
            "in": "query",
            "required": false,
            "description": "Return only cards from this bank.",
            "schema": {
              "type": "string",
              "enum": [
                "top500",
                "top1000",
                "top2000",
                "mined",
                "random"
              ]
            }
          },
          {
            "name": "excludeWords",
            "in": "query",
            "required": false,
            "description": "Comma-separated words to leave out of the round.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cards to practise, in random order.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ClozeCard"
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:read"
      }
    },
    "/api/cloze/onboarding": {
      "get": {
        "operationId": "getApiClozeOnboarding",
        "summary": "Get the guided first-run cards",
        "tags": [
          "Practice"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/LanguageQuery"
          },
          {
            "name": "vocabIds",
            "in": "query",
            "required": true,
            "description": "Comma-separated vocabulary identifiers. Between 1 and 20.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The cards, in the order you asked for.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ClozeCard"
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:read"
      },
      "post": {
        "operationId": "postApiClozeOnboarding",
        "summary": "Create a guided first-run card",
        "description": "The identifier comes from `vocabId`, so a repeat call updates the same card.",
        "tags": [
          "Practice"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "vocabId": {
                    "type": "string"
                  },
                  "word": {
                    "type": "string"
                  },
                  "sentence": {
                    "type": "string"
                  },
                  "translation": {
                    "type": "string"
                  },
                  "language": {
                    "type": "string"
                  }
                },
                "required": [
                  "vocabId",
                  "word",
                  "sentence",
                  "translation",
                  "language"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The card already existed and is updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClozeCard"
                }
              }
            }
          },
          "201": {
            "description": "The card is created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClozeCard"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:write"
      }
    },
    "/api/cloze/seed": {
      "get": {
        "operationId": "getApiClozeSeed",
        "summary": "Check whether the card bank needs a seed",
        "tags": [
          "Practice"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "Card counts, and the advice to seed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "dbCount": {
                      "type": "integer",
                      "description": "Cards the account holds."
                    },
                    "bankSize": {
                      "type": "integer",
                      "description": "Cards in the built-in bank."
                    },
                    "needsSeed": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:read"
      },
      "post": {
        "operationId": "postApiClozeSeed",
        "summary": "Seed cards from the built-in bank",
        "description": "Adds the missing cards for the language. Repeat calls are safe.",
        "tags": [
          "Practice"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "What the seed changed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "seeded": {
                      "type": "integer"
                    },
                    "updated": {
                      "type": "integer"
                    },
                    "mined": {
                      "type": "integer"
                    },
                    "tatoeba": {
                      "type": "integer"
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:write"
      }
    },
    "/api/cloze/stats": {
      "get": {
        "operationId": "getApiClozeStats",
        "summary": "Get lifetime practice totals",
        "tags": [
          "Practice"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "Correct and incorrect answers, summed over every card.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "timesCorrect": {
                      "type": "integer"
                    },
                    "timesIncorrect": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:read"
      }
    },
    "/api/collections": {
      "get": {
        "operationId": "getApiCollections",
        "summary": "List collections",
        "description": "Collections in the language, in sort order.",
        "tags": [
          "Library"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "The account’s collections.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CollectionListItem"
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:read"
      },
      "post": {
        "operationId": "postApiCollections",
        "summary": "Create a collection",
        "tags": [
          "Library"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "author": {
                    "type": "string"
                  },
                  "coverUrl": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "groupId": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "language": {
                    "type": "string"
                  },
                  "id": {
                    "type": "string",
                    "description": "Supply your own identifier. Optional."
                  }
                },
                "required": [
                  "title"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new collection.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatedId"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:write"
      }
    },
    "/api/collections/{id}": {
      "delete": {
        "operationId": "deleteApiCollectionsById",
        "summary": "Delete a collection",
        "description": "Deletes the collection, its lessons, and any audio on disk.",
        "tags": [
          "Library"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Collection identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "The collection is deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:write"
      },
      "get": {
        "operationId": "getApiCollectionsById",
        "summary": "Get a collection",
        "tags": [
          "Library"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Collection identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "The collection.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CollectionDetail"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such record, in this account and language.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:read"
      },
      "put": {
        "operationId": "putApiCollectionsById",
        "summary": "Update a collection",
        "tags": [
          "Library"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Collection identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Send only the fields to change.",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "author": {
                    "type": "string"
                  },
                  "coverUrl": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "groupId": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The collection is updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:write"
      }
    },
    "/api/collections/{id}/lessons": {
      "get": {
        "operationId": "getApiCollectionsByIdLessons",
        "summary": "List the lessons of a collection",
        "tags": [
          "Library"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Collection identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "Lessons in sort order.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/LessonListItem"
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:read"
      },
      "post": {
        "operationId": "postApiCollectionsByIdLessons",
        "summary": "Add a lesson to a collection",
        "tags": [
          "Library"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Collection identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "textContent": {
                    "type": "string",
                    "description": "Lesson text, as Markdown."
                  },
                  "sortOrder": {
                    "type": "integer"
                  },
                  "id": {
                    "type": "string",
                    "description": "Supply your own identifier. Optional."
                  }
                },
                "required": [
                  "title",
                  "textContent"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new lesson.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatedId"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such collection, in this account and language.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:write"
      }
    },
    "/api/collections/{id}/lessons/reorder": {
      "put": {
        "operationId": "putApiCollectionsByIdLessonsReorder",
        "summary": "Reorder the lessons of a collection",
        "tags": [
          "Library"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Collection identifier.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdList"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new order is stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:write"
      }
    },
    "/api/collections/reorder": {
      "put": {
        "operationId": "putApiCollectionsReorder",
        "summary": "Reorder collections",
        "description": "Send the collections of one group in their new order.",
        "tags": [
          "Library"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdList"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new order is stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:write"
      }
    },
    "/api/data": {
      "get": {
        "operationId": "getApiData",
        "summary": "Export the learning data",
        "description": "Returns every portable record for the account. Credentials, billing state and cached shared content stay out.",
        "tags": [
          "Data"
        ],
        "responses": {
          "200": {
            "description": "The export.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserExport"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "data:export"
      },
      "post": {
        "operationId": "postApiData",
        "summary": "Restore learning data",
        "description": "Writes the records of an export into the account. The restored rows become yours, whatever the file says. One restore runs at a time.",
        "tags": [
          "Data"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UserExport"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The restore finished. `imported` counts the rows per table.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "imported": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "integer"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A restore is already running for the account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "The restore capacity is busy. Try again shortly.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "data:import"
      }
    },
    "/api/dictionary/cache": {
      "post": {
        "operationId": "postApiDictionaryCache",
        "summary": "Accept a translation into the dictionary",
        "description": "Stores a machine translation the user accepted. Later lookups of the word answer from the cache, at no cost.",
        "tags": [
          "Dictionary"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "word": {
                    "type": "string"
                  },
                  "language": {
                    "type": "string"
                  },
                  "senses": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "partOfSpeech": {
                          "type": "string"
                        },
                        "gloss": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "partOfSpeech",
                        "gloss"
                      ]
                    },
                    "minItems": 1
                  },
                  "ipa": {
                    "type": "string"
                  },
                  "etymology": {
                    "type": "string"
                  }
                },
                "required": [
                  "word",
                  "senses"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The entry is cached.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "word": {
                      "type": "string",
                      "description": "The stored word key."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:write"
      }
    },
    "/api/dictionary/lookup": {
      "get": {
        "operationId": "getApiDictionaryLookup",
        "summary": "Look up a word",
        "description": "Searches the built-in dictionary, its inflection tables, and the translations you accepted. A miss answers `200` with a null entry, so fall back to `POST /api/translate`.",
        "tags": [
          "Dictionary"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/LanguageQuery"
          },
          {
            "name": "word",
            "in": "query",
            "required": true,
            "description": "The word to look up.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The entry, or null on a miss.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "entry": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/DictionaryEntry"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "entry"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:read"
      }
    },
    "/api/explain": {
      "post": {
        "operationId": "postApiExplain",
        "summary": "Explain a practice sentence",
        "description": "Explains the grammar of a sentence, and why the blanked word fits.",
        "tags": [
          "Language help"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "sentence": {
                    "type": "string"
                  },
                  "translation": {
                    "type": "string"
                  },
                  "clozeWord": {
                    "type": "string"
                  },
                  "language": {
                    "type": "string"
                  }
                },
                "required": [
                  "sentence",
                  "translation"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The explanation, as Markdown.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "explanation": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:read"
      }
    },
    "/api/extract-url": {
      "post": {
        "operationId": "postApiExtractUrl",
        "summary": "Extract an article from a URL",
        "description": "Fetches the page and returns its readable article as Markdown. Stores nothing.",
        "tags": [
          "Import"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string"
                  }
                },
                "required": [
                  "url"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The extracted article.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "title": {
                      "type": "string"
                    },
                    "author": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "content": {
                      "type": "string",
                      "description": "The article, as Markdown."
                    },
                    "siteName": {
                      "type": "string"
                    },
                    "excerpt": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "wordCount": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The URL is not valid, or the page is too large.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "enum": [
                        "INVALID_URL",
                        "FETCH_FAILED",
                        "EXTRACTION_FAILED"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:write"
      }
    },
    "/api/groups": {
      "get": {
        "operationId": "getApiGroups",
        "summary": "List collection groups",
        "description": "Groups hold collections of every language, so the count crosses languages.",
        "tags": [
          "Library"
        ],
        "responses": {
          "200": {
            "description": "Groups in sort order.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CollectionGroup"
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:read"
      },
      "post": {
        "operationId": "postApiGroups",
        "summary": "Create a group",
        "tags": [
          "Library"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new group.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatedId"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:write"
      }
    },
    "/api/groups/{id}": {
      "delete": {
        "operationId": "deleteApiGroupsById",
        "summary": "Delete a group",
        "description": "The collections survive. They become ungrouped.",
        "tags": [
          "Library"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Group identifier.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The group is deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:write"
      },
      "put": {
        "operationId": "putApiGroupsById",
        "summary": "Rename or reorder a group",
        "tags": [
          "Library"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Group identifier.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "sortOrder": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The group is updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:write"
      }
    },
    "/api/import/audio": {
      "post": {
        "operationId": "postApiImportAudio",
        "summary": "Import an audio file",
        "description": "Stores the audio and creates a pending lesson. A background worker writes the transcript, so poll the lesson for `transcriptionStatus`.",
        "tags": [
          "Import"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The audio file."
                  },
                  "title": {
                    "type": "string"
                  },
                  "language": {
                    "type": "string"
                  },
                  "groupId": {
                    "type": "string"
                  }
                },
                "required": [
                  "file"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The audio is stored and the transcript is queued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "collectionId": {
                      "type": "string"
                    },
                    "lessonId": {
                      "type": "string"
                    },
                    "title": {
                      "type": "string"
                    },
                    "audioDurationMs": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "transcriptionStatus": {
                      "type": "string",
                      "enum": [
                        "pending"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:write"
      }
    },
    "/api/import/epub": {
      "post": {
        "operationId": "postApiImportEpub",
        "summary": "Import an EPUB",
        "description": "Creates one collection, and one lesson per chapter.",
        "tags": [
          "Import"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The EPUB file."
                  },
                  "language": {
                    "type": "string"
                  },
                  "groupId": {
                    "type": "string",
                    "description": "Put the new collection in this group."
                  }
                },
                "required": [
                  "file"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The import finished.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "collectionId": {
                      "type": "string"
                    },
                    "title": {
                      "type": "string"
                    },
                    "author": {
                      "type": "string"
                    },
                    "lessonCount": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:write"
      }
    },
    "/api/import/youtube": {
      "post": {
        "operationId": "postApiImportYoutube",
        "summary": "Import a caption track as a lesson",
        "tags": [
          "Import"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string"
                  },
                  "trackId": {
                    "type": "string",
                    "description": "A track from the resolve call."
                  },
                  "language": {
                    "type": "string"
                  },
                  "groupId": {
                    "type": "string"
                  }
                },
                "required": [
                  "url"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The lesson is created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:write"
      }
    },
    "/api/import/youtube/resolve": {
      "post": {
        "operationId": "postApiImportYoutubeResolve",
        "summary": "List the caption tracks of a video",
        "description": "Reads the video metadata and its caption tracks. Stores nothing.",
        "tags": [
          "Import"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "A YouTube watch URL."
                  }
                },
                "required": [
                  "url"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Video metadata and the available tracks.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:write"
      }
    },
    "/api/journal": {
      "get": {
        "operationId": "getApiJournal",
        "summary": "List journal entries",
        "tags": [
          "Journal"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/LanguageQuery"
          },
          {
            "name": "date",
            "in": "query",
            "required": false,
            "description": "Return only entries with this entry date.",
            "schema": {
              "type": "string",
              "description": "Calendar date, `YYYY-MM-DD`."
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum entries. The default is 20.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Entries to skip. The default is 0.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching entries.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/JournalEntry"
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:read"
      },
      "post": {
        "operationId": "postApiJournal",
        "summary": "Create a journal entry",
        "tags": [
          "Journal"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body": {
                    "type": "string",
                    "description": "The text of the entry."
                  },
                  "entryDate": {
                    "type": "string",
                    "description": "Calendar date, `YYYY-MM-DD`."
                  },
                  "language": {
                    "type": "string"
                  }
                },
                "required": [
                  "body"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new entry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "entryDate": {
                      "type": "string",
                      "description": "Calendar date, `YYYY-MM-DD`."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:write"
      }
    },
    "/api/journal-correct": {
      "post": {
        "operationId": "postApiJournalCorrect",
        "summary": "Correct text without storing it",
        "description": "Corrects a piece of text and returns the result. Nothing is stored.",
        "tags": [
          "Journal"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body": {
                    "type": "string",
                    "description": "The text to correct."
                  },
                  "language": {
                    "type": "string"
                  }
                },
                "required": [
                  "body"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The correction. Nothing is stored.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "correctedBody": {
                      "type": "string"
                    },
                    "corrections": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Correction"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:read"
      }
    },
    "/api/journal/{id}": {
      "delete": {
        "operationId": "deleteApiJournalById",
        "summary": "Delete a journal entry",
        "tags": [
          "Journal"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Entry identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "The entry is deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such record, in this account and language.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:write"
      },
      "get": {
        "operationId": "getApiJournalById",
        "summary": "Get a journal entry",
        "tags": [
          "Journal"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Entry identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "The entry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JournalEntry"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such record, in this account and language.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:read"
      },
      "put": {
        "operationId": "putApiJournalById",
        "summary": "Update a journal entry",
        "tags": [
          "Journal"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Entry identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body": {
                    "type": "string"
                  }
                },
                "required": [
                  "body"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The entry is updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "400": {
            "description": "The entry is already submitted, so the text is locked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such record, in this account and language.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:write"
      }
    },
    "/api/journal/{id}/correct": {
      "post": {
        "operationId": "postApiJournalByIdCorrect",
        "summary": "Correct a journal entry",
        "description": "Runs the language model over the entry, stores the corrected text, and sets the status to `submitted`.",
        "tags": [
          "Journal"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Entry identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "The correction.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "correctedBody": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "corrections": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Correction"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such record, in this account and language.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:write"
      }
    },
    "/api/known-words": {
      "get": {
        "operationId": "getApiKnownWords",
        "summary": "Get every word knowledge state",
        "description": "Returns one map for the language. The reader colours words from it.",
        "tags": [
          "Vocabulary"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "Word to state map.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KnownWordMap"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:read"
      },
      "post": {
        "operationId": "postApiKnownWords",
        "summary": "Update word knowledge states in bulk",
        "tags": [
          "Vocabulary"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "language": {
                    "type": "string"
                  },
                  "updates": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "word": {
                          "type": "string"
                        },
                        "state": {
                          "type": "string",
                          "enum": [
                            "new",
                            "level1",
                            "level2",
                            "level3",
                            "level4",
                            "known",
                            "ignored"
                          ],
                          "description": "How well the account knows a word. `level1` to `level4` are the learning steps between `new` and `known`. `ignored` hides the word."
                        }
                      },
                      "required": [
                        "word",
                        "state"
                      ]
                    }
                  }
                },
                "required": [
                  "updates"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The states are stored.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "count": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:write"
      }
    },
    "/api/learner-events": {
      "post": {
        "operationId": "postApiLearnerEvents",
        "summary": "Record a learner event",
        "description": "Stores one product analytics event for the account. Send an `idempotencyKey` to make a retry safe.",
        "tags": [
          "Onboarding"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "eventType": {
                    "type": "string",
                    "enum": [
                      "onboarding.started",
                      "onboarding.profile_saved",
                      "onboarding.skipped",
                      "lesson.opened",
                      "reader.term_looked_up",
                      "vocab.saved",
                      "vocab.state_changed",
                      "practice.answer_submitted",
                      "practice.round_completed",
                      "onboarding.completed"
                    ]
                  },
                  "language": {
                    "type": "string"
                  },
                  "lessonId": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Required for `lesson.opened`."
                  },
                  "vocabId": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Required for the two `vocab.*` events."
                  },
                  "properties": {
                    "type": "object"
                  },
                  "idempotencyKey": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "eventType",
                  "language"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The event is a repeat, so nothing changed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "201": {
            "description": "The event is recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "stats:write"
      }
    },
    "/api/lessons/{id}": {
      "delete": {
        "operationId": "deleteApiLessonsById",
        "summary": "Delete a lesson",
        "tags": [
          "Library"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Lesson identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "The lesson is deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:write"
      },
      "get": {
        "operationId": "getApiLessonsById",
        "summary": "Get a lesson",
        "tags": [
          "Library"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Lesson identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "The lesson.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Lesson"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such record, in this account and language.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:read"
      },
      "put": {
        "operationId": "putApiLessonsById",
        "summary": "Update a lesson",
        "tags": [
          "Library"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Lesson identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Send only the fields to change.",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "textContent": {
                    "type": "string"
                  },
                  "collectionId": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "sortOrder": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The lesson is updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:write"
      }
    },
    "/api/lessons/{id}/audio": {
      "get": {
        "operationId": "getApiLessonsByIdAudio",
        "summary": "Stream the audio of a lesson",
        "description": "Serves the audio file. The endpoint honours the `Range` header and answers `206` with `Content-Range`, so a player can seek.",
        "tags": [
          "Library"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Lesson identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "The complete audio file.",
            "content": {
              "audio/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "206": {
            "description": "The requested byte range.",
            "content": {
              "audio/*": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The lesson has no audio, or the stored file is gone.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:read"
      }
    },
    "/api/lessons/{id}/progress": {
      "put": {
        "operationId": "putApiLessonsByIdProgress",
        "summary": "Store reading progress",
        "tags": [
          "Library"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Lesson identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "scrollPosition": {
                    "type": "number"
                  },
                  "percentComplete": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 100
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The progress is stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such record, in this account and language.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:write"
      }
    },
    "/api/lessons/{id}/retry-transcription": {
      "post": {
        "operationId": "postApiLessonsByIdRetryTranscription",
        "summary": "Retry a failed transcription",
        "description": "Puts a failed audio lesson back in the transcription queue.",
        "tags": [
          "Library"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Lesson identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "The lesson is queued again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such lesson, or its transcription did not fail.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:write"
      }
    },
    "/api/lessons/{id}/segments": {
      "get": {
        "operationId": "getApiLessonsByIdSegments",
        "summary": "Get the timed transcript of a lesson",
        "description": "Returns the audio-timed lines for listen-along. The array is empty until transcription finishes, and for text lessons.",
        "tags": [
          "Library"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Lesson identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "Segments in playback order.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/TranscriptSegment"
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such record, in this account and language.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "collections:read"
      }
    },
    "/api/llm-status": {
      "get": {
        "operationId": "getApiLlmStatus",
        "summary": "Check the language model provider",
        "description": "Reports the provider, the model, and the result of a health check.",
        "tags": [
          "Settings"
        ],
        "responses": {
          "200": {
            "description": "Provider health.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "provider": {
                      "type": "string"
                    },
                    "model": {
                      "type": "string"
                    },
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "settings:read"
      }
    },
    "/api/llm-status/reset": {
      "post": {
        "operationId": "postApiLlmStatusReset",
        "summary": "Clear the cached provider",
        "description": "Call this after a provider setting changes, so the next call builds a new client.",
        "tags": [
          "Settings"
        ],
        "responses": {
          "200": {
            "description": "The cache is clear.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "settings:write"
      }
    },
    "/api/llm-status/test": {
      "post": {
        "operationId": "postApiLlmStatusTest",
        "summary": "Send a test completion",
        "description": "Asks the provider for one tiny completion. The call spends monthly allowance.",
        "tags": [
          "Settings"
        ],
        "responses": {
          "200": {
            "description": "The provider answered.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "response": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The provider failed. The allowance is returned.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    },
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "settings:write"
      }
    },
    "/api/llm/openai/models": {
      "post": {
        "operationId": "postApiLlmOpenaiModels",
        "summary": "List the models of an OpenAI-compatible endpoint",
        "description": "Asks the endpoint for its model list, from the server. Self-hosted deployments only. Cloud answers `404`.",
        "tags": [
          "Settings"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "endpoint": {
                    "type": "string",
                    "description": "Base URL of the endpoint."
                  },
                  "apiKey": {
                    "type": "string",
                    "description": "Omit it to use the stored key."
                  }
                },
                "required": [
                  "endpoint"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The models.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "models": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not available on this deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The endpoint refused the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "chat:write"
      }
    },
    "/api/onboarding": {
      "get": {
        "operationId": "getApiOnboarding",
        "summary": "Get the guided first-run state",
        "description": "Returns the profile, the current step, and the recommended lesson. An account with a language but no row is an existing user.",
        "tags": [
          "Onboarding"
        ],
        "responses": {
          "200": {
            "description": "The state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OnboardingSnapshot"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "stats:read"
      },
      "patch": {
        "operationId": "patchApiOnboarding",
        "summary": "Advance the guided first run",
        "tags": [
          "Onboarding"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "currentStep": {
                    "type": "string",
                    "enum": [
                      "reader",
                      "practice",
                      "summary"
                    ]
                  },
                  "nextLessonId": {
                    "type": "string"
                  },
                  "nextLessonTitle": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OnboardingSnapshot"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The guided first run has not started.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "stats:write"
      }
    },
    "/api/onboarding/complete": {
      "post": {
        "operationId": "postApiOnboardingComplete",
        "summary": "Finish the guided first run",
        "tags": [
          "Onboarding"
        ],
        "responses": {
          "200": {
            "description": "The new state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OnboardingSnapshot"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The guided first run has not started.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "stats:write"
      }
    },
    "/api/onboarding/skip": {
      "post": {
        "operationId": "postApiOnboardingSkip",
        "summary": "Skip the guided first run",
        "description": "Stores the profile and the target language, then marks the run skipped.",
        "tags": [
          "Onboarding"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "language": {
                    "type": "string"
                  },
                  "approximateLevel": {
                    "type": "string",
                    "enum": [
                      "new",
                      "beginner",
                      "intermediate",
                      "advanced",
                      "not_sure"
                    ]
                  },
                  "interests": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Supported interest names."
                  },
                  "dailyMinutes": {
                    "type": "integer",
                    "minimum": 5,
                    "maximum": 120
                  }
                },
                "required": [
                  "language",
                  "approximateLevel",
                  "interests",
                  "dailyMinutes"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OnboardingSnapshot"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "stats:write"
      }
    },
    "/api/onboarding/start": {
      "post": {
        "operationId": "postApiOnboardingStart",
        "summary": "Start the guided first run",
        "description": "Stores the learner profile and picks a starter lesson.",
        "tags": [
          "Onboarding"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "language": {
                    "type": "string"
                  },
                  "approximateLevel": {
                    "type": "string",
                    "enum": [
                      "new",
                      "beginner",
                      "intermediate",
                      "advanced",
                      "not_sure"
                    ]
                  },
                  "interests": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Supported interest names."
                  },
                  "dailyMinutes": {
                    "type": "integer",
                    "minimum": 5,
                    "maximum": 120
                  }
                },
                "required": [
                  "language",
                  "approximateLevel",
                  "interests",
                  "dailyMinutes"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OnboardingSnapshot"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "stats:write"
      }
    },
    "/api/settings": {
      "get": {
        "operationId": "getApiSettings",
        "summary": "Get every setting",
        "description": "Returns one object, keyed by setting name. A credential reads back as `true`, never as its value.",
        "tags": [
          "Settings"
        ],
        "responses": {
          "200": {
            "description": "The settings of the account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "settings:read"
      },
      "put": {
        "operationId": "putApiSettings",
        "summary": "Write settings in bulk",
        "description": "Writes every pair in the body. The API rejects the whole batch if one key is unknown, so a bad call changes nothing.",
        "tags": [
          "Settings"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Setting name to value. Only the listed names are accepted.",
                "propertyNames": {
                  "enum": [
                    "targetLanguage",
                    "timezone",
                    "llmProvider",
                    "openaiPreset",
                    "openaiUrl",
                    "openaiModel",
                    "openaiApiKey",
                    "anthropicApiKey",
                    "claudeOauthToken",
                    "anthropicAuthMode",
                    "ankiConnectUrl",
                    "ankiTransport",
                    "ollamaModel",
                    "apfelUrl",
                    "apfelModel",
                    "lmstudioUrl",
                    "lmstudioModel",
                    "lmstudioApiKey"
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The settings are stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "settings:write"
      }
    },
    "/api/settings/{key}": {
      "delete": {
        "operationId": "deleteApiSettingsByKey",
        "summary": "Delete one setting",
        "tags": [
          "Settings"
        ],
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "description": "Setting name.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The setting is deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "settings:write"
      },
      "get": {
        "operationId": "getApiSettingsByKey",
        "summary": "Get one setting",
        "description": "Returns the value, or null when the account has no such setting.",
        "tags": [
          "Settings"
        ],
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "description": "Setting name.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The value. A credential reads back as `true`.",
            "content": {
              "application/json": {
                "schema": {
                  "description": "Any JSON value, or null."
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "settings:read"
      },
      "put": {
        "operationId": "putApiSettingsByKey",
        "summary": "Write one setting",
        "tags": [
          "Settings"
        ],
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "description": "Setting name.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "value": {
                    "description": "Any JSON value."
                  }
                },
                "required": [
                  "value"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The setting is stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "settings:write"
      }
    },
    "/api/stats": {
      "get": {
        "operationId": "getApiStats",
        "summary": "List daily statistics",
        "description": "One row per day, oldest first. Give a date range, or a number of days.",
        "tags": [
          "Statistics"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/LanguageQuery"
          },
          {
            "name": "startDate",
            "in": "query",
            "required": false,
            "description": "First day of the range. Pair it with `endDate`.",
            "schema": {
              "type": "string",
              "description": "Calendar date, `YYYY-MM-DD`."
            }
          },
          {
            "name": "endDate",
            "in": "query",
            "required": false,
            "description": "Last day of the range.",
            "schema": {
              "type": "string",
              "description": "Calendar date, `YYYY-MM-DD`."
            }
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "Days back from today. Ignored when a range is given.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Daily rows.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/DailyStats"
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "stats:read"
      }
    },
    "/api/stats/activity": {
      "get": {
        "operationId": "getApiStatsActivity",
        "summary": "Get daily activity for the heatmap",
        "description": "Sums each day across every language, to match the streak.",
        "tags": [
          "Statistics"
        ],
        "responses": {
          "200": {
            "description": "One row per day, oldest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "date": {
                        "type": "string",
                        "description": "Calendar date, `YYYY-MM-DD`."
                      },
                      "dictionaryLookups": {
                        "type": "integer"
                      },
                      "clozePracticed": {
                        "type": "integer"
                      },
                      "minutesRead": {
                        "type": "integer"
                      },
                      "ankiReviews": {
                        "type": "integer"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "stats:read"
      }
    },
    "/api/stats/fluency": {
      "get": {
        "operationId": "getApiStatsFluency",
        "summary": "Get the fluency estimate",
        "description": "Counts words by state, estimates a CEFR band, and reports growth over two weeks.",
        "tags": [
          "Statistics"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "The estimate.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "totalKnownWords": {
                      "type": "integer"
                    },
                    "totalLearning": {
                      "type": "integer"
                    },
                    "totalNew": {
                      "type": "integer"
                    },
                    "byState": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "integer"
                      }
                    },
                    "byDomain": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      },
                      "description": "Per-domain axes for the radar chart."
                    },
                    "pending": {
                      "type": "integer",
                      "description": "Words with no domain yet."
                    },
                    "estimatedLevel": {
                      "type": "string"
                    },
                    "nextLevel": {
                      "type": "string"
                    },
                    "progressToNextLevel": {
                      "type": "number"
                    },
                    "wordsToNextLevel": {
                      "type": "integer"
                    },
                    "weeklyGrowth": {
                      "type": "object",
                      "properties": {
                        "thisWeek": {
                          "type": "integer"
                        },
                        "lastWeek": {
                          "type": "integer"
                        },
                        "delta": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "stats:read"
      }
    },
    "/api/stats/reading": {
      "get": {
        "operationId": "getApiStatsReading",
        "summary": "Get estimated reading volume",
        "description": "Derived from the scroll progress of each lesson in the language.",
        "tags": [
          "Statistics"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "Reading totals.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "wordsRead": {
                      "type": "integer",
                      "description": "Estimated words read, over every lesson."
                    },
                    "totalWords": {
                      "type": "integer",
                      "description": "Words in the library."
                    },
                    "lessonsTotal": {
                      "type": "integer"
                    },
                    "lessonsStarted": {
                      "type": "integer"
                    },
                    "lessonsCompleted": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "stats:read"
      }
    },
    "/api/stats/streak": {
      "get": {
        "operationId": "getApiStatsStreak",
        "summary": "Get the study streak",
        "description": "One value for the whole account. A day counts as active after any lookup, practice, reading time or Anki review, in any language.",
        "tags": [
          "Statistics"
        ],
        "responses": {
          "200": {
            "description": "The streak.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "streak": {
                      "type": "integer",
                      "description": "Days in the current streak."
                    },
                    "longest": {
                      "type": "integer"
                    },
                    "practicedToday": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "stats:read"
      }
    },
    "/api/stats/today": {
      "get": {
        "operationId": "getApiStatsToday",
        "summary": "Get the statistics for today",
        "description": "Creates the row for today if it does not exist. Day rollover follows the time zone setting.",
        "tags": [
          "Statistics"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "The row for today.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DailyStats"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "stats:read"
      },
      "put": {
        "operationId": "putApiStatsToday",
        "summary": "Add to a counter for today",
        "description": "Adds `amount` to one counter. The call is an increment, not a set.",
        "tags": [
          "Statistics"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "field": {
                    "type": "string",
                    "enum": [
                      "wordsRead",
                      "newWordsSaved",
                      "wordsMarkedKnown",
                      "minutesRead",
                      "clozePracticed",
                      "points",
                      "dictionaryLookups",
                      "ankiReviews"
                    ]
                  },
                  "amount": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "The default is 1."
                  }
                },
                "required": [
                  "field"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The counter is updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "stats:write"
      }
    },
    "/api/study-ping": {
      "get": {
        "operationId": "getApiStudyPing",
        "summary": "Check whether study happened today",
        "description": "Aggregated across every language. Built for an external agent to poll.",
        "tags": [
          "Statistics"
        ],
        "responses": {
          "200": {
            "description": "The activity for today.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "done": {
                      "type": "boolean"
                    },
                    "date": {
                      "type": "string",
                      "description": "Calendar date, `YYYY-MM-DD`."
                    },
                    "minutes": {
                      "type": "integer"
                    },
                    "lookups": {
                      "type": "integer"
                    },
                    "clozePracticed": {
                      "type": "integer"
                    },
                    "sessionStartedAt": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "stats:read"
      },
      "post": {
        "operationId": "postApiStudyPing",
        "summary": "Record the start of a study session",
        "description": "Stores the session start time once per day, on the row of the active language.",
        "tags": [
          "Statistics"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "The activity for today.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "done": {
                      "type": "boolean"
                    },
                    "date": {
                      "type": "string",
                      "description": "Calendar date, `YYYY-MM-DD`."
                    },
                    "minutes": {
                      "type": "integer"
                    },
                    "lookups": {
                      "type": "integer"
                    },
                    "sessionStartedAt": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "stats:write"
      }
    },
    "/api/tatoeba": {
      "get": {
        "operationId": "getApiTatoeba",
        "summary": "Search example sentences",
        "description": "Reads the Tatoeba corpus and returns sentences with an English translation.",
        "tags": [
          "Language help"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/LanguageQuery"
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "description": "Search text. Omit it for random sentences.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum sentences. The ceiling is 100, and the default is 20.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching sentences.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sentences": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "text": {
                            "type": "string"
                          },
                          "lang": {
                            "type": "string"
                          },
                          "translation": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "integer"
                              },
                              "text": {
                                "type": "string"
                              },
                              "lang": {
                                "type": "string"
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:read"
      }
    },
    "/api/translate": {
      "post": {
        "operationId": "postApiTranslate",
        "summary": "Translate a word or phrase",
        "description": "Asks the language model for a translation in context. Look the word up in the dictionary first: a cached hit costs nothing.",
        "tags": [
          "Language help"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "word": {
                    "type": "string",
                    "description": "The word or phrase. A word must be one token."
                  },
                  "type": {
                    "type": "string",
                    "enum": [
                      "word",
                      "phrase"
                    ]
                  },
                  "sentence": {
                    "type": "string",
                    "description": "The sentence around it. Improves the result."
                  },
                  "language": {
                    "type": "string"
                  }
                },
                "required": [
                  "word",
                  "type"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A phrase returns a plain translation. A word returns a full entry.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "properties": {
                        "translation": {
                          "type": "string"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "translation": {
                          "type": "string",
                          "description": "Every sense gloss, joined."
                        },
                        "partOfSpeech": {
                          "type": "string",
                          "description": "Part of speech of the first sense."
                        },
                        "word": {
                          "type": "string"
                        },
                        "senses": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "partOfSpeech": {
                                "type": "string"
                              },
                              "gloss": {
                                "type": "string"
                              }
                            }
                          }
                        },
                        "ipa": {
                          "type": "string"
                        },
                        "etymology": {
                          "type": "string"
                        },
                        "relatedForms": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "form": {
                                "type": "string"
                              },
                              "relation": {
                                "type": "string"
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "The plan allowance or the burst limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "retryAfterSeconds": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:read"
      }
    },
    "/api/translate/enrich": {
      "post": {
        "operationId": "postApiTranslateEnrich",
        "summary": "Enrich a word entry",
        "description": "Returns pronunciation, etymology and related forms as well as the senses. The Free plan needs your own provider key for this call.",
        "tags": [
          "Language help"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "word": {
                    "type": "string",
                    "description": "One token."
                  },
                  "sentence": {
                    "type": "string"
                  },
                  "language": {
                    "type": "string"
                  }
                },
                "required": [
                  "word"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The enriched entry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "translation": {
                      "type": "string",
                      "description": "Every sense gloss, joined."
                    },
                    "partOfSpeech": {
                      "type": "string",
                      "description": "Part of speech of the first sense."
                    },
                    "word": {
                      "type": "string"
                    },
                    "senses": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "partOfSpeech": {
                            "type": "string"
                          },
                          "gloss": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "ipa": {
                      "type": "string"
                    },
                    "etymology": {
                      "type": "string"
                    },
                    "relatedForms": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "form": {
                            "type": "string"
                          },
                          "relation": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:read"
      }
    },
    "/api/translate/gloss": {
      "post": {
        "operationId": "postApiTranslateGloss",
        "summary": "Get a short gloss for a word",
        "description": "The fast path the reader uses when the dictionary misses. Returns one short meaning.",
        "tags": [
          "Language help"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "word": {
                    "type": "string",
                    "description": "One token."
                  },
                  "sentence": {
                    "type": "string"
                  },
                  "language": {
                    "type": "string"
                  }
                },
                "required": [
                  "word"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The gloss, streamed as plain text. Read the body as a stream.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:read"
      }
    },
    "/api/tts": {
      "post": {
        "operationId": "postApiTts",
        "summary": "Synthesize speech",
        "description": "Returns the audio as base64. Some language packs have no voice, and answer `404` with `noAudio`. Do not fall back to a browser voice for those packs.",
        "tags": [
          "Language help"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string",
                    "description": "At most 5,000 bytes."
                  },
                  "rate": {
                    "type": "number",
                    "description": "Speaking rate. The default is 0.9."
                  },
                  "language": {
                    "type": "string"
                  }
                },
                "required": [
                  "text"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The audio.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "audioContent": {
                      "type": "string",
                      "description": "Base64 audio."
                    },
                    "contentType": {
                      "type": "string",
                      "enum": [
                        "audio/mp3",
                        "audio/wav"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The language pack has no synthesized voice.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "noAudio": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "This deployment has no voice configured. Use a local voice instead.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "fallback": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:read"
      }
    },
    "/api/vocab": {
      "get": {
        "operationId": "getApiVocab",
        "summary": "List saved words and phrases",
        "tags": [
          "Vocabulary"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/LanguageQuery"
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "description": "Return only entries in this state.",
            "schema": {
              "type": "string",
              "enum": [
                "new",
                "level1",
                "level2",
                "level3",
                "level4",
                "known",
                "ignored"
              ],
              "description": "How well the account knows a word. `level1` to `level4` are the learning steps between `new` and `known`. `ignored` hides the word."
            }
          },
          {
            "name": "bookId",
            "in": "query",
            "required": false,
            "description": "Return only entries from this collection.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "unpushed",
            "in": "query",
            "required": false,
            "description": "Set to `true` to return only entries that Anki does not hold yet.",
            "schema": {
              "type": "string",
              "enum": [
                "true"
              ]
            }
          },
          {
            "name": "text",
            "in": "query",
            "required": false,
            "description": "Return only the entry with this exact text. Fold the word first.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching entries.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/VocabEntry"
                  }
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:read"
      },
      "post": {
        "operationId": "postApiVocab",
        "summary": "Save a word or phrase",
        "tags": [
          "Vocabulary"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string"
                  },
                  "type": {
                    "type": "string",
                    "enum": [
                      "word",
                      "phrase"
                    ]
                  },
                  "sentence": {
                    "type": "string"
                  },
                  "translation": {
                    "type": "string"
                  },
                  "state": {
                    "type": "string",
                    "enum": [
                      "new",
                      "level1",
                      "level2",
                      "level3",
                      "level4",
                      "known",
                      "ignored"
                    ],
                    "description": "How well the account knows a word. `level1` to `level4` are the learning steps between `new` and `known`. `ignored` hides the word."
                  },
                  "bookId": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "chapter": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "language": {
                    "type": "string"
                  },
                  "id": {
                    "type": "string",
                    "description": "Supply your own identifier. Optional."
                  }
                },
                "required": [
                  "text"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new entry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatedId"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:write"
      }
    },
    "/api/vocab/{id}": {
      "delete": {
        "operationId": "deleteApiVocabById",
        "summary": "Delete a saved entry",
        "tags": [
          "Vocabulary"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Vocabulary entry identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "The entry is deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such record, in this account and language.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:write"
      },
      "get": {
        "operationId": "getApiVocabById",
        "summary": "Get one saved entry",
        "tags": [
          "Vocabulary"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Vocabulary entry identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "The entry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VocabEntry"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such record, in this account and language.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:read"
      },
      "put": {
        "operationId": "putApiVocabById",
        "summary": "Update a saved entry",
        "tags": [
          "Vocabulary"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Vocabulary entry identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/LanguageQuery"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Send only the fields to change.",
                "properties": {
                  "text": {
                    "type": "string"
                  },
                  "sentence": {
                    "type": "string"
                  },
                  "translation": {
                    "type": "string"
                  },
                  "state": {
                    "type": "string",
                    "enum": [
                      "new",
                      "level1",
                      "level2",
                      "level3",
                      "level4",
                      "known",
                      "ignored"
                    ],
                    "description": "How well the account knows a word. `level1` to `level4` are the learning steps between `new` and `known`. `ignored` hides the word."
                  },
                  "reviewCount": {
                    "type": "integer"
                  },
                  "pushedToAnki": {
                    "type": "integer",
                    "enum": [
                      0,
                      1
                    ]
                  },
                  "ankiNoteId": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The entry is updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "400": {
            "description": "The body is malformed, or a field is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The credential is missing, invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token does not carry the scope this endpoint needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such record, in this account and language.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "A plan limit or a rate limit stopped the call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanLimitError"
                }
              }
            }
          },
          "500": {
            "description": "The API failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "PersonalAccessToken": []
          },
          {
            "SessionCookie": []
          }
        ],
        "x-token-scope": "vocab:write"
      }
    },
    "/health": {
      "get": {
        "operationId": "getHealth",
        "summary": "Check that the service runs",
        "description": "Answers without a credential. Reports the deployment mode.",
        "tags": [
          "Service"
        ],
        "responses": {
          "200": {
            "description": "The service runs.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "mode": {
                      "type": "string",
                      "enum": [
                        "selfhost",
                        "cloud"
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "security": []
      }
    }
  },
  "components": {
    "securitySchemes": {
      "PersonalAccessToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "A personal access token from Settings, sent as `Authorization: Bearer <token>`. The token needs the scope named on the endpoint."
      },
      "SessionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "__Secure-better-auth.session_token",
        "description": "The session cookie of the browser client. Lector Cloud only. The name is `__Secure-better-auth.session_token` over https, and `better-auth.session_token` on a plain http deployment."
      }
    },
    "parameters": {
      "LanguageQuery": {
        "name": "language",
        "in": "query",
        "required": false,
        "description": "Language pack code. Defaults to the account’s active language.",
        "schema": {
          "type": "string",
          "examples": [
            "af",
            "es",
            "grc"
          ]
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "What went wrong."
          }
        },
        "required": [
          "error"
        ]
      },
      "PlanLimitError": {
        "type": "object",
        "description": "A plan limit stopped the call.",
        "properties": {
          "error": {
            "type": "string"
          },
          "limit": {
            "type": "string",
            "description": "Name of the limit that was reached."
          },
          "plan": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ]
      },
      "Success": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "enum": [
              true
            ]
          }
        },
        "required": [
          "success"
        ]
      },
      "CreatedId": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Identifier of the new record."
          }
        },
        "required": [
          "id"
        ]
      },
      "IdList": {
        "type": "object",
        "description": "Records in their new order. `sortOrder` becomes the array index.",
        "properties": {
          "ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "ids"
        ]
      },
      "Collection": {
        "type": "object",
        "description": "A book, course or folder of lessons in one language. These are the stored fields, and the data takeout carries exactly these.",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "author": {
            "type": "string"
          },
          "coverUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "groupId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Group that holds the collection."
          },
          "language": {
            "type": "string"
          },
          "sortOrder": {
            "type": "integer"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastReadAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "title",
          "author",
          "sortOrder",
          "createdAt",
          "lastReadAt"
        ]
      },
      "CollectionListItem": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Collection"
          },
          {
            "type": "object",
            "description": "The list adds the group name and the lesson totals.",
            "properties": {
              "groupName": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Name of the group that holds it."
              },
              "lessonCount": {
                "type": "integer"
              },
              "avgProgress": {
                "type": "number",
                "description": "Mean reading progress over its lessons, from 0 to 100."
              }
            }
          }
        ]
      },
      "CollectionDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Collection"
          },
          {
            "type": "object",
            "description": "The single-collection read adds the lesson totals.",
            "properties": {
              "lessonCount": {
                "type": "integer"
              },
              "avgProgress": {
                "type": "number",
                "description": "Mean reading progress over its lessons, from 0 to 100."
              }
            }
          }
        ]
      },
      "CollectionGroup": {
        "type": "object",
        "description": "A container for collections. Groups hold every language.",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "sortOrder": {
            "type": "integer"
          },
          "collectionCount": {
            "type": "integer",
            "description": "Collections in the group, counted across every language."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "name",
          "sortOrder",
          "createdAt"
        ]
      },
      "Lesson": {
        "type": "object",
        "description": "One readable text, and its audio when the source carried audio.",
        "properties": {
          "id": {
            "type": "string"
          },
          "collectionId": {
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "type": "string"
          },
          "sortOrder": {
            "type": "integer"
          },
          "textContent": {
            "type": "string",
            "description": "The lesson text, as Markdown."
          },
          "wordCount": {
            "type": "integer"
          },
          "language": {
            "type": "string"
          },
          "progress_scrollPosition": {
            "type": "number"
          },
          "progress_percentComplete": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "sourceType": {
            "type": [
              "string",
              "null"
            ],
            "description": "Origin of the text, for example `youtube`. Null for plain Markdown."
          },
          "sourceMeta": {
            "type": [
              "string",
              "null"
            ],
            "description": "Origin metadata, as a JSON string."
          },
          "segmentWords": {
            "type": [
              "string",
              "null"
            ],
            "description": "The distinct word forms a segmenter found, as a JSON string array. Null for every spaced language."
          },
          "audioDurationMs": {
            "type": [
              "integer",
              "null"
            ]
          },
          "audioBytes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "transcriptionStatus": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "pending",
              "processing",
              "done",
              "error",
              null
            ],
            "description": "Transcription state of an audio lesson. Null for a text lesson."
          },
          "transcriptionError": {
            "type": [
              "string",
              "null"
            ]
          },
          "transcriptionAttempts": {
            "type": "integer"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastReadAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "title",
          "sortOrder",
          "textContent",
          "wordCount",
          "createdAt"
        ]
      },
      "LessonListItem": {
        "type": "object",
        "description": "A lesson as the collection listing returns it. The text stays out, so fetch the lesson itself to read it.",
        "properties": {
          "id": {
            "type": "string"
          },
          "collectionId": {
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "type": "string"
          },
          "sortOrder": {
            "type": "integer"
          },
          "wordCount": {
            "type": "integer"
          },
          "progress_scrollPosition": {
            "type": "number"
          },
          "progress_percentComplete": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "audioDurationMs": {
            "type": [
              "integer",
              "null"
            ]
          },
          "transcriptionStatus": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "pending",
              "processing",
              "done",
              "error",
              null
            ]
          },
          "transcriptionError": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastReadAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "title",
          "sortOrder",
          "wordCount",
          "createdAt"
        ]
      },
      "TranscriptSegment": {
        "type": "object",
        "description": "One timed line of an audio transcript.",
        "properties": {
          "idx": {
            "type": "integer",
            "description": "Position in playback order."
          },
          "startMs": {
            "type": "integer"
          },
          "endMs": {
            "type": "integer"
          },
          "text": {
            "type": "string"
          }
        },
        "required": [
          "idx",
          "startMs",
          "endMs",
          "text"
        ]
      },
      "VocabEntry": {
        "type": "object",
        "description": "A word or phrase the account saved, with the sentence it came from.",
        "properties": {
          "id": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "word",
              "phrase"
            ]
          },
          "sentence": {
            "type": "string",
            "description": "Sentence that held the word."
          },
          "translation": {
            "type": "string"
          },
          "state": {
            "type": "string",
            "enum": [
              "new",
              "level1",
              "level2",
              "level3",
              "level4",
              "known",
              "ignored"
            ],
            "description": "How well the account knows a word. `level1` to `level4` are the learning steps between `new` and `known`. `ignored` hides the word."
          },
          "stateUpdatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "reviewCount": {
            "type": "integer"
          },
          "bookId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Collection the word came from."
          },
          "chapter": {
            "type": [
              "integer",
              "null"
            ]
          },
          "language": {
            "type": "string"
          },
          "pushedToAnki": {
            "type": "integer",
            "enum": [
              0,
              1
            ]
          },
          "ankiNoteId": {
            "type": [
              "integer",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "text",
          "type",
          "state",
          "language",
          "createdAt"
        ]
      },
      "KnownWordMap": {
        "type": "object",
        "description": "Every rated word in the language, as a word to state map.",
        "additionalProperties": {
          "type": "string",
          "enum": [
            "new",
            "level1",
            "level2",
            "level3",
            "level4",
            "known",
            "ignored"
          ],
          "description": "How well the account knows a word. `level1` to `level4` are the learning steps between `new` and `known`. `ignored` hides the word."
        }
      },
      "ClozeCard": {
        "type": "object",
        "description": "A practice sentence with one word blanked out, plus its review schedule.",
        "properties": {
          "id": {
            "type": "string"
          },
          "sentence": {
            "type": "string"
          },
          "clozeWord": {
            "type": "string",
            "description": "The word the learner must supply."
          },
          "clozeIndex": {
            "type": "integer",
            "description": "Position of the blanked word in the sentence, counted in words."
          },
          "translation": {
            "type": "string"
          },
          "language": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "enum": [
              "tatoeba",
              "mined"
            ]
          },
          "collection": {
            "type": "string",
            "enum": [
              "top500",
              "top1000",
              "top2000",
              "mined",
              "random"
            ]
          },
          "wordRank": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Frequency rank of the blanked word."
          },
          "tatoebaSentenceId": {
            "type": [
              "integer",
              "null"
            ]
          },
          "vocabEntryId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Vocabulary entry the card was mined from."
          },
          "masteryLevel": {
            "type": "integer",
            "enum": [
              0,
              25,
              50,
              75,
              100
            ]
          },
          "nextReview": {
            "type": "string",
            "format": "date-time"
          },
          "lastReviewed": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "reviewCount": {
            "type": "integer"
          },
          "timesCorrect": {
            "type": "integer"
          },
          "timesIncorrect": {
            "type": "integer"
          },
          "blacklisted": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "1 hides the card."
          }
        },
        "required": [
          "id",
          "sentence",
          "clozeWord",
          "clozeIndex",
          "masteryLevel",
          "nextReview"
        ]
      },
      "DailyStats": {
        "type": "object",
        "description": "One day of activity in one language.",
        "properties": {
          "date": {
            "type": "string",
            "description": "Calendar date, `YYYY-MM-DD`."
          },
          "language": {
            "type": "string"
          },
          "wordsRead": {
            "type": "integer"
          },
          "newWordsSaved": {
            "type": "integer"
          },
          "wordsMarkedKnown": {
            "type": "integer"
          },
          "minutesRead": {
            "type": "integer"
          },
          "clozePracticed": {
            "type": "integer"
          },
          "points": {
            "type": "integer"
          },
          "dictionaryLookups": {
            "type": "integer"
          },
          "ankiReviews": {
            "type": "integer"
          },
          "sessionStartedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "date"
        ]
      },
      "Correction": {
        "type": "object",
        "description": "One correction that the language model made to a journal entry.",
        "properties": {
          "original": {
            "type": "string",
            "description": "The wrong word or phrase."
          },
          "corrected": {
            "type": "string"
          },
          "explanation": {
            "type": "string",
            "description": "Why the original is wrong."
          },
          "type": {
            "type": "string",
            "enum": [
              "grammar",
              "spelling",
              "word_choice",
              "word_order",
              "missing_word",
              "extra_word"
            ]
          }
        }
      },
      "JournalEntry": {
        "type": "object",
        "description": "A piece of writing in the target language, and its correction.",
        "properties": {
          "id": {
            "type": "string"
          },
          "body": {
            "type": "string"
          },
          "correctedBody": {
            "type": [
              "string",
              "null"
            ]
          },
          "corrections": {
            "type": [
              "array",
              "null"
            ],
            "description": "The corrections. Null before a correction runs.",
            "items": {
              "$ref": "#/components/schemas/Correction"
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "submitted"
            ]
          },
          "wordCount": {
            "type": "integer"
          },
          "language": {
            "type": "string"
          },
          "entryDate": {
            "type": "string",
            "description": "Calendar date, `YYYY-MM-DD`."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "body",
          "status",
          "wordCount",
          "language",
          "entryDate"
        ]
      },
      "DictionaryEntry": {
        "type": "object",
        "description": "A dictionary result for one word.",
        "properties": {
          "word": {
            "type": "string"
          },
          "rank": {
            "type": "integer",
            "description": "Frequency rank in the language."
          },
          "ipa": {
            "type": "string"
          },
          "etymology": {
            "type": "string"
          },
          "senses": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "partOfSpeech": {
                  "type": "string"
                },
                "gloss": {
                  "type": "string"
                }
              },
              "required": [
                "partOfSpeech",
                "gloss"
              ]
            }
          },
          "relatedForms": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "form": {
                  "type": "string"
                },
                "relation": {
                  "type": "string"
                }
              },
              "required": [
                "form",
                "relation"
              ]
            }
          },
          "lemmaInfo": {
            "type": "object",
            "description": "Set when the lookup matched an inflected form.",
            "properties": {
              "stem": {
                "type": "string"
              },
              "label": {
                "type": "string"
              }
            }
          },
          "source": {
            "type": "string",
            "enum": [
              "dict",
              "cache"
            ],
            "description": "`dict` is the built-in dictionary. `cache` is a translation you accepted."
          }
        },
        "required": [
          "word",
          "senses"
        ]
      },
      "OnboardingSnapshot": {
        "type": "object",
        "description": "The guided first-run state of the account.",
        "properties": {
          "progress": {
            "type": [
              "object",
              "null"
            ],
            "description": "Null before the account starts or skips the guided first run.",
            "properties": {
              "version": {
                "type": "integer"
              },
              "status": {
                "type": "string",
                "enum": [
                  "in_progress",
                  "completed",
                  "skipped"
                ]
              },
              "currentStep": {
                "type": "string",
                "enum": [
                  "reader",
                  "practice",
                  "summary"
                ]
              },
              "language": {
                "type": "string"
              },
              "starterCollectionId": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "recommendedLessonId": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "recommendedLessonTitle": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "nextLessonId": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "nextLessonTitle": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "startedAt": {
                "type": "string",
                "format": "date-time"
              },
              "completedAt": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "updatedAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "profile": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "language": {
                "type": "string"
              },
              "approximateLevel": {
                "type": "string",
                "enum": [
                  "new",
                  "beginner",
                  "intermediate",
                  "advanced",
                  "not_sure"
                ]
              },
              "interests": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "dailyMinutes": {
                "type": "integer"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              },
              "updatedAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "events": {
            "type": "array",
            "description": "The learner events since the guided first run started.",
            "items": {
              "$ref": "#/components/schemas/LearnerEvent"
            }
          }
        }
      },
      "LearnerEvent": {
        "type": "object",
        "description": "One recorded product analytics event.",
        "properties": {
          "id": {
            "type": "string"
          },
          "eventType": {
            "type": "string"
          },
          "language": {
            "type": "string"
          },
          "lessonId": {
            "type": [
              "string",
              "null"
            ]
          },
          "vocabId": {
            "type": [
              "string",
              "null"
            ]
          },
          "properties": {
            "type": "object"
          },
          "idempotencyKey": {
            "type": [
              "string",
              "null"
            ]
          },
          "occurredAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "LessonExport": {
        "type": "object",
        "description": "A lesson as the data takeout carries it. The text travels, and the audio and transcription state stay behind.",
        "properties": {
          "id": {
            "type": "string"
          },
          "collectionId": {
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "type": "string"
          },
          "sortOrder": {
            "type": "integer"
          },
          "textContent": {
            "type": "string",
            "description": "The lesson text, as Markdown."
          },
          "wordCount": {
            "type": "integer"
          },
          "language": {
            "type": "string"
          },
          "progress_scrollPosition": {
            "type": "number"
          },
          "progress_percentComplete": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastReadAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "title",
          "sortOrder",
          "textContent",
          "wordCount",
          "createdAt"
        ]
      },
      "JournalEntryExport": {
        "type": "object",
        "description": "A journal entry as the data takeout carries it. `corrections` stays a JSON string here, while the journal endpoints parse it.",
        "properties": {
          "id": {
            "type": "string"
          },
          "body": {
            "type": "string"
          },
          "correctedBody": {
            "type": [
              "string",
              "null"
            ]
          },
          "corrections": {
            "type": [
              "string",
              "null"
            ],
            "description": "The corrections, as a JSON string."
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "submitted"
            ]
          },
          "wordCount": {
            "type": "integer"
          },
          "language": {
            "type": "string"
          },
          "entryDate": {
            "type": "string",
            "description": "Calendar date, `YYYY-MM-DD`."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "body",
          "status",
          "wordCount",
          "language",
          "entryDate"
        ]
      },
      "UserExport": {
        "type": "object",
        "description": "Every portable learning record for the account.",
        "properties": {
          "format": {
            "type": "string",
            "enum": [
              "lector-learning-data"
            ]
          },
          "version": {
            "type": "integer",
            "enum": [
              1
            ]
          },
          "exportedAt": {
            "type": "string",
            "format": "date-time"
          },
          "collections": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Collection"
            }
          },
          "collectionGroups": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CollectionGroup"
            }
          },
          "lessons": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LessonExport"
            }
          },
          "vocab": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VocabEntry"
            }
          },
          "knownWords": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "word": {
                  "type": "string"
                },
                "language": {
                  "type": "string"
                },
                "state": {
                  "type": "string",
                  "enum": [
                    "new",
                    "level1",
                    "level2",
                    "level3",
                    "level4",
                    "known",
                    "ignored"
                  ],
                  "description": "How well the account knows a word. `level1` to `level4` are the learning steps between `new` and `known`. `ignored` hides the word."
                },
                "domain": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Topic the classifier assigned."
                }
              }
            }
          },
          "clozeSentences": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClozeCard"
            }
          },
          "journalEntries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/JournalEntryExport"
            }
          },
          "dailyStats": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DailyStats"
            }
          },
          "acceptedDictionaryEntries": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "learnerProfiles": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "onboardingProgress": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "learnerEvents": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "settings": {
            "type": "array",
            "description": "Only the portable pair: `targetLanguage` and `timezone`.",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string"
                },
                "value": {
                  "type": "string"
                }
              }
            }
          }
        },
        "required": [
          "format",
          "version",
          "exportedAt"
        ]
      }
    }
  }
}
