{
  "openapi": "3.1.1",
  "info": {
    "title": "Spaced Repetition Scheduler (FSRS) API",
    "version": "1.0.0",
    "summary": "Which flashcards are due today? FSRS-5 or SM-2 from your review log, with a 30-day forecast.",
    "description": "Every endpoint performs the same calculation as the human page and returns inputs, result, units, assumptions, sources, warnings and computedAt. MCP endpoint: /mcp."
  },
  "servers": [
    {
      "url": "https://deckwrightreviewschedule.usefulweb.site"
    }
  ],
  "paths": {
    "/api/v1/calculate": {
      "post": {
        "operationId": "calculate",
        "summary": "Which flashcards are due today? FSRS-5 or SM-2 from your review log, with a 30-day forecast.",
        "description": "Replays a review log (date, card, rating again/hard/good/easy) through the FSRS-5 spaced-repetition model or the classic SM-2 algorithm and returns today’s queue (cards relearned today, due reviews, new cards up to your daily limit), the next interval for every answer, each card’s stability, difficulty and recall probability, a 30-day forecast and study statistics. The same scheduler runs Deckwright’s Study view.\n\nMethodology:\n- Card states are replayed from the log in date order. FSRS-5: first rating G sets stability S = w[G−1] and difficulty D = w4 − e^(w5(G−1)) + 1; later reviews use the recall probability R = (1 + 19/81 · t/S)^−0.5 after t days to grow S (Hard × w15, Easy × w16) or, after Again, to set the post-lapse stability; reviews on the same day use S·e^(w17(G−3+w18)); D moves by −w6(G−3) with linear damping and mean reversion to D0(Easy).\n- The next interval is the number of days until R falls to the desired retention: I = S/(19/81) · (r^(−2) − 1), rounded, at least 1 day. Again keeps the card in today’s queue until it is answered Hard or better.\n- SM-2: grades Again = 1, Hard = 3, Good = 4, Easy = 5; EF changes by 0.1 − (5−q)(0.08 + (5−q)·0.02) (at least 1.3); intervals 1, 6, then the previous interval × EF rounded up; a grade below 3 restarts the repetitions; grades below 4 repeat the card the same day.\n- Queue: cards answered Again today first, then due reviews (oldest due date first, up to the daily maximum), then new cards in their order in the set (up to the new-card limit minus the new cards already started today). True retention counts the first answer of each day for cards seen before, over the last 30 days.\n\nFSRS runs with its published default parameters (no optimisation on your own history) and day-level steps without random fuzz. Recall probabilities are model estimates.",
        "tags": [
          "apps"
        ],
        "x-mcp-tool": "deckwright_review_schedule",
        "x-owner-app": "deckwright",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeckwrightReviewScheduleInput"
              },
              "examples": {
                "example1": {
                  "summary": "Spanish cards after one week (FSRS-5)",
                  "value": {
                    "text": "# Greetings\nhola ::: hello\nbuenos días ::: good morning\ngracias ::: thank you\n\n# Getting around\nQ: How do you ask where the station is?\nA: ¿Dónde está la estación?\n\nla estación :: the station\nel billete :: the ticket\n\n# At the café\nun café con leche :: a coffee with milk\n\nQ: How do you ask for the bill?\nA: la cuenta, por favor\n\nMadrid's main station is {{c1::Atocha}}; the fast train to Barcelona takes about {{c2::two and a half::how long?}} hours.\n",
                    "log": "date,card,rating\n2026-11-02,hola,good\n2026-11-02,buenos días,good\n2026-11-02,gracias,again\n2026-11-02,gracias,good\n2026-11-05,hola,good\n2026-11-05,gracias,hard\n2026-11-06,buenos días,easy\n2026-11-07,la estación,good\n",
                    "today": "2026-11-08"
                  }
                },
                "example2": {
                  "summary": "The same log with SM-2",
                  "value": {
                    "text": "# Greetings\nhola ::: hello\nbuenos días ::: good morning\ngracias ::: thank you\n\n# Getting around\nQ: How do you ask where the station is?\nA: ¿Dónde está la estación?\n\nla estación :: the station\nel billete :: the ticket\n\n# At the café\nun café con leche :: a coffee with milk\n\nQ: How do you ask for the bill?\nA: la cuenta, por favor\n\nMadrid's main station is {{c1::Atocha}}; the fast train to Barcelona takes about {{c2::two and a half::how long?}} hours.\n",
                    "log": "date,card,rating\n2026-11-02,hola,good\n2026-11-02,buenos días,good\n2026-11-02,gracias,again\n2026-11-02,gracias,good\n2026-11-05,hola,good\n2026-11-05,gracias,hard\n2026-11-06,buenos días,easy\n2026-11-07,la estación,good\n",
                    "today": "2026-11-08",
                    "scheduler": "sm2"
                  }
                },
                "example3": {
                  "summary": "Higher retention target (0.95), 5 new cards a day",
                  "value": {
                    "text": "# Greetings\nhola ::: hello\nbuenos días ::: good morning\ngracias ::: thank you\n\n# Getting around\nQ: How do you ask where the station is?\nA: ¿Dónde está la estación?\n\nla estación :: the station\nel billete :: the ticket\n\n# At the café\nun café con leche :: a coffee with milk\n\nQ: How do you ask for the bill?\nA: la cuenta, por favor\n\nMadrid's main station is {{c1::Atocha}}; the fast train to Barcelona takes about {{c2::two and a half::how long?}} hours.\n",
                    "log": "date,card,rating\n2026-11-02,hola,good\n2026-11-02,buenos días,good\n2026-11-02,gracias,again\n2026-11-02,gracias,good\n2026-11-05,hola,good\n2026-11-05,gracias,hard\n2026-11-06,buenos días,easy\n2026-11-07,la estación,good\n",
                    "today": "2026-11-08",
                    "retention": 0.95,
                    "newPerDay": 5
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeckwrightReviewScheduleResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tool": {
      "get": {
        "operationId": "getToolDefinition",
        "summary": "Inputs, output schema and examples for this tool",
        "responses": {
          "200": {
            "description": "Tool definition.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details. 422 responses include per-field `errors`.",
        "required": [
          "type",
          "title",
          "status",
          "detail"
        ],
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "field",
                "message"
              ],
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "DeckwrightReviewScheduleInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "text": {
            "title": "Cards",
            "description": "Markdown: “Q:” and “A:” lines, “term :: definition” (“:::” also makes the reverse card), {{c1::cloze}} gaps, # headings as tags. Or CSV/TSV rows (front, back, tag), an Anki plain-text export, or a slide deck.",
            "type": "string",
            "maxLength": 200000,
            "default": "# Greetings\nhola ::: hello\nbuenos días ::: good morning\ngracias ::: thank you\n\n# Getting around\nQ: How do you ask where the station is?\nA: ¿Dónde está la estación?\n\nla estación :: the station\nel billete :: the ticket\n\n# At the café\nun café con leche :: a coffee with milk\n\nQ: How do you ask for the bill?\nA: la cuenta, por favor\n\nMadrid's main station is {{c1::Atocha}}; the fast train to Barcelona takes about {{c2::two and a half::how long?}} hours.\n"
          },
          "format": {
            "title": "Format",
            "type": "string",
            "enum": [
              "auto",
              "markdown",
              "csv",
              "tsv",
              "anki",
              "deck"
            ],
            "default": "auto"
          },
          "reverse": {
            "title": "Also make reverse cards (answer → question)",
            "type": "boolean",
            "default": false
          },
          "log": {
            "title": "Review log (CSV: date, card, rating)",
            "description": "One review per line: 2026-11-02,hola,good — the card is its id or its question text; the rating is again, hard, good, easy or 1–4. Deckwright’s “Export review log” writes this format.",
            "type": "string",
            "maxLength": 2000000,
            "default": "date,card,rating\n2026-11-02,hola,good\n2026-11-02,buenos días,good\n2026-11-02,gracias,again\n2026-11-02,gracias,good\n2026-11-05,hola,good\n2026-11-05,gracias,hard\n2026-11-06,buenos días,easy\n2026-11-07,la estación,good\n"
          },
          "today": {
            "title": "Today",
            "description": "The day to plan for; empty = today (UTC).",
            "type": "string",
            "format": "date"
          },
          "scheduler": {
            "title": "Scheduler",
            "type": "string",
            "enum": [
              "fsrs",
              "sm2"
            ],
            "default": "fsrs"
          },
          "retention": {
            "title": "Desired retention (FSRS)",
            "description": "Probability of remembering a card when it comes due; higher means shorter intervals and more reviews.",
            "type": "number",
            "minimum": 0.7,
            "maximum": 0.97,
            "default": 0.9
          },
          "newPerDay": {
            "title": "New cards per day",
            "type": "integer",
            "minimum": 0,
            "maximum": 999,
            "default": 20
          },
          "maxReviews": {
            "title": "Maximum reviews per day",
            "type": "integer",
            "minimum": 0,
            "maximum": 9999,
            "default": 200
          }
        },
        "required": []
      },
      "DeckwrightReviewScheduleResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "today",
              "scheduler",
              "retention",
              "queue",
              "cards",
              "forecast",
              "stats",
              "logEntries",
              "warnings"
            ],
            "properties": {
              "today": {
                "type": "string"
              },
              "scheduler": {
                "enum": [
                  "fsrs",
                  "sm2"
                ]
              },
              "retention": {
                "type": "number"
              },
              "logEntries": {
                "type": "integer"
              },
              "queue": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "n",
                    "id",
                    "front",
                    "kind",
                    "due",
                    "overdueDays",
                    "retrievability",
                    "next"
                  ],
                  "properties": {
                    "n": {
                      "type": "integer"
                    },
                    "id": {
                      "type": "string"
                    },
                    "front": {
                      "type": "string"
                    },
                    "kind": {
                      "enum": [
                        "new",
                        "learning",
                        "review"
                      ]
                    },
                    "due": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "overdueDays": {
                      "type": "integer"
                    },
                    "retrievability": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "next": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "again",
                        "hard",
                        "good",
                        "easy"
                      ],
                      "properties": {
                        "again": {
                          "type": "integer"
                        },
                        "hard": {
                          "type": "integer"
                        },
                        "good": {
                          "type": "integer"
                        },
                        "easy": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              },
              "cards": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "id",
                    "front",
                    "phase",
                    "reps",
                    "lapses",
                    "stability",
                    "difficulty",
                    "ease",
                    "interval",
                    "last",
                    "due",
                    "retrievability"
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "front": {
                      "type": "string"
                    },
                    "phase": {
                      "enum": [
                        "new",
                        "learning",
                        "review",
                        "relearning"
                      ]
                    },
                    "reps": {
                      "type": "integer"
                    },
                    "lapses": {
                      "type": "integer"
                    },
                    "stability": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "difficulty": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "ease": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "interval": {
                      "type": "integer"
                    },
                    "last": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "due": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "retrievability": {
                      "type": [
                        "number",
                        "null"
                      ]
                    }
                  }
                }
              },
              "forecast": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "date",
                    "count"
                  ],
                  "properties": {
                    "date": {
                      "type": "string"
                    },
                    "count": {
                      "type": "integer"
                    }
                  }
                }
              },
              "stats": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "cards",
                  "new",
                  "learning",
                  "young",
                  "mature",
                  "dueToday",
                  "newToday",
                  "reviewsTotal",
                  "retention30",
                  "averageRetrievability",
                  "streak",
                  "nextDue"
                ],
                "properties": {
                  "cards": {
                    "type": "integer"
                  },
                  "new": {
                    "type": "integer"
                  },
                  "learning": {
                    "type": "integer"
                  },
                  "young": {
                    "type": "integer"
                  },
                  "mature": {
                    "type": "integer"
                  },
                  "dueToday": {
                    "type": "integer"
                  },
                  "newToday": {
                    "type": "integer"
                  },
                  "reviewsTotal": {
                    "type": "integer"
                  },
                  "retention30": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "averageRetrievability": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "streak": {
                    "type": "integer"
                  },
                  "nextDue": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "warnings": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      }
    }
  }
}