{
  "openapi": "3.1.1",
  "info": {
    "title": "Ledgerly Savings Goal Calculator API",
    "version": "1.0.0",
    "summary": "How much to save each month for a goal by a date — or how long a monthly amount takes.",
    "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://ledgerlysavingsgoal.usefulweb.app"
    }
  ],
  "paths": {
    "/api/v1/calculate": {
      "post": {
        "operationId": "calculate",
        "summary": "How much to save each month for a goal by a date — or how long a monthly amount takes.",
        "description": "Works out the monthly amount you need to reach a savings goal by a target date, or how long it takes with the amount you can put aside. Includes what you already have, interest with monthly, yearly or daily compounding, fees and an option to keep the goal in today’s money so it grows with inflation. A small table shows the effect of a higher or lower return. Part of Ledgerly’s Debt \u0026 Growth.\n\nMethodology:\n- Each month the balance grows by the monthly rate, the fee (yearly percentage ÷ 12) is taken off, and the monthly amount is paid in at the end of the month.\n- Monthly amount for n months: P = (goal − saved · gⁿ) · (g − 1) / (gⁿ − 1), where g is the monthly growth factor after fees; rounded up to the cent. With no interest P = (goal − saved) / n.\n- Time needed: the balance is simulated month by month until it reaches the goal (at most 100 years).\n- A goal in today’s money grows with inflation every month, so it keeps its buying power.\n\nInterest rates and returns change; recheck the plan every year. Not financial advice.",
        "tags": [
          "apps"
        ],
        "x-mcp-tool": "ledgerly_savings_goal",
        "x-owner-app": "ledgerly",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LedgerlySavingsGoalInput"
              },
              "examples": {
                "example1": {
                  "summary": "20,000 in 5 years with 2,000 saved at 3 %",
                  "value": {
                    "currency": "EUR",
                    "mode": "monthly-needed",
                    "targetAmount": 20000,
                    "startAmount": 2000,
                    "years": 5,
                    "annualReturnPercent": 3,
                    "startMonth": "2027-01"
                  }
                },
                "example2": {
                  "summary": "How long: 30,000 in today’s money with 400 a month",
                  "value": {
                    "currency": "EUR",
                    "mode": "time-needed",
                    "targetAmount": 30000,
                    "targetIn": "today",
                    "startAmount": 5000,
                    "monthlyContribution": 400,
                    "annualReturnPercent": 4,
                    "inflationPercent": 2
                  }
                },
                "example3": {
                  "summary": "Holiday fund: 3,000 in 18 months, no interest",
                  "value": {
                    "currency": "GBP",
                    "mode": "monthly-needed",
                    "targetAmount": 3000,
                    "startAmount": 0,
                    "years": 1.5,
                    "annualReturnPercent": 0
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LedgerlySavingsGoalResult"
                }
              }
            }
          },
          "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"
                }
              }
            }
          }
        }
      },
      "LedgerlySavingsGoalInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "currency": {
            "title": "Currency",
            "description": "ISO code used for the labels, e.g. EUR, USD, GBP.",
            "type": "string",
            "maxLength": 3,
            "default": "EUR"
          },
          "mode": {
            "title": "What to find",
            "type": "string",
            "enum": [
              "monthly-needed",
              "time-needed"
            ],
            "default": "monthly-needed"
          },
          "targetAmount": {
            "title": "Savings goal",
            "type": "number",
            "minimum": 1,
            "maximum": 1000000000,
            "default": 20000
          },
          "targetIn": {
            "title": "The goal is in",
            "type": "string",
            "enum": [
              "nominal",
              "today"
            ],
            "default": "nominal"
          },
          "startAmount": {
            "title": "Already saved",
            "type": "number",
            "minimum": 0,
            "maximum": 1000000000,
            "default": 2000
          },
          "years": {
            "title": "Years until the goal (for the monthly amount)",
            "type": "number",
            "minimum": 0.25,
            "maximum": 80,
            "default": 5
          },
          "monthlyContribution": {
            "title": "Monthly amount (for the time needed)",
            "type": "number",
            "minimum": 0,
            "maximum": 10000000,
            "default": 300
          },
          "annualReturnPercent": {
            "title": "Interest or return (% per year)",
            "type": "number",
            "minimum": -20,
            "maximum": 30,
            "default": 3
          },
          "compounding": {
            "title": "Compounding",
            "type": "string",
            "enum": [
              "monthly",
              "yearly",
              "daily"
            ],
            "default": "monthly"
          },
          "feePercent": {
            "title": "Yearly fees (% of the balance)",
            "type": "number",
            "minimum": 0,
            "maximum": 10,
            "default": 0
          },
          "inflationPercent": {
            "title": "Inflation (% per year)",
            "type": "number",
            "minimum": -5,
            "maximum": 30,
            "default": 2
          },
          "startMonth": {
            "title": "First saving month (YYYY-MM, optional)",
            "description": "Gives the month you reach the goal; leave empty to count months only.",
            "type": "string",
            "maxLength": 7,
            "default": ""
          }
        },
        "required": []
      },
      "LedgerlySavingsGoalResult": {
        "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": [
              "currency",
              "mode",
              "targetAmount",
              "targetAtDate",
              "months",
              "reachedMonth",
              "monthlyNeeded",
              "monthlyContribution",
              "finalBalance",
              "totalContributed",
              "totalGrowth",
              "sensitivity",
              "path"
            ],
            "properties": {
              "currency": {
                "type": "string"
              },
              "mode": {
                "type": "string",
                "enum": [
                  "monthly-needed",
                  "time-needed"
                ]
              },
              "targetAmount": {
                "type": "number"
              },
              "targetAtDate": {
                "type": "number"
              },
              "months": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "reachedMonth": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "monthlyNeeded": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "monthlyContribution": {
                "type": "number"
              },
              "finalBalance": {
                "type": "number"
              },
              "totalContributed": {
                "type": "number"
              },
              "totalGrowth": {
                "type": "number"
              },
              "sensitivity": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "returnPercent",
                    "monthlyNeeded",
                    "months"
                  ],
                  "properties": {
                    "returnPercent": {
                      "type": "number"
                    },
                    "monthlyNeeded": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "months": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    }
                  }
                }
              },
              "path": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "month",
                    "balance",
                    "target"
                  ],
                  "properties": {
                    "month": {
                      "type": "integer"
                    },
                    "balance": {
                      "type": "number"
                    },
                    "target": {
                      "type": "number"
                    }
                  }
                }
              }
            }
          },
          "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"
              }
            }
          }
        }
      }
    }
  }
}