The modeling database for AI agents.
Spreadsheets are for eyes. Finicast is for agents: named line items, a real calendar, incremental recalculation, scenarios, and an explain for every number. Open-source engine (FiniDB); hosted at finicast.com.
For humans
Tell your agent:
Read https://v1.finicast.com/agents and build me a three-year forecast from this CSV.
For agents
If you are an AI agent, this is your path. Total time: under a minute.
1. Connect to a FiniDB engine.
Local (no account needed):
npx finidb init ./model
claude mcp add finidb -- npx finidb mcp --data ./model
Hosted (your human creates a project at https://v1.finicast.com and gives you a project id and token):
claude mcp add finicast -- npx finidb mcp --url https://v1.finicast.com --db <project> --token <token>
2. Read the guide (about 2,000 tokens): https://v1.finicast.com/guide
It covers the object model (lists, calendar, tables, modules, line items,
scoped rules, views, scenarios), the formula language, ten idioms, and the
diagnostic codes.
3. Build.
finidb_describe → what exists
finidb_import_csv → land facts in a table; lists are created from the data
finidb_apply_spec → declare calendar, lists, modules, rules, views in one call
finidb_query / finidb_explain → read numbers back and check them
4. Verify before you hand back.
finidb_list_errors must be empty. Use dry_run on finidb_apply_spec when you
are not sure. Tell the human what you changed; in the hosted product they
see your commands, the changed cells and the spec diff, and can restore.
5. Deliver.
finidb_export kind=csv gives the human a grid; kind=spec gives a
diffable model file.
Everything is name-based. There are no cell addresses. A formula looks like
revenue = PREV(revenue) * (1 + growth)
and a rule can be scoped:
amount {scenario: forecast, time: "> @last_actual"} = PREV(amount) * (1 + drivers.rev_growth)
Worked example, copy-pasteable: https://v1.finicast.com/examples/income-statement.md
An income statement where history and forecast carry different rules, with annual roll-ups.
Full API: https://v1.finicast.com/docs/api Full guide: https://v1.finicast.com/guideExample: an income statement, history and forecast
Quarterly actuals through 2026-Q2, a driver-based forecast to 2027-Q4, and annual columns the calendar rolls up. The historical and forecast quarters of the same line item are governed by different rules. Two calls build it.
| Line item | Historical quarters frame: hist | Forecast quarters frame: fcst | Annual column |
|---|---|---|---|
| revenue | actual, typed in | PREV(revenue) * (1 + revenue_growth) | sum of the quarters |
| revenue_growth | revenue / PREV(revenue) - 1 | your assumption, typed in | same formula, prior year |
| cogs | actual, typed in | revenue * (1 - gross_margin) | sum of the quarters |
| gross_profit | revenue - cogs | revenue - cogs | sum of the quarters |
| gross_margin | gross_profit / revenue | your assumption, typed in | same formula, annual totals |
| opex | actual, typed in | PREV(opex) * (1 + 3%) | sum of the quarters |
| operating_income | gross_profit - opex | gross_profit - opex | sum of the quarters |
| operating_margin | operating_income / revenue | operating_income / revenue | same formula, annual totals |
What comes back
| period | 2025-Q1 | 2025-Q2 | 2025-Q3 | 2025-Q4 | 2025 | 2026-Q1 | 2026-Q2 | 2026-Q3 | 2026-Q4 | 2026 | 2027-Q1 | 2027-Q2 | 2027-Q3 | 2027-Q4 | 2027 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| frame | hist | hist | hist | hist | hist | hist | hist | fcst | fcst | fcst | fcst | fcst | fcst | fcst | fcst |
| revenue | 100.0 | 110.0 | 120.0 | 130.0 | 460.0 | 140.0 | 150.0 | 157.5 | 165.4 | 612.9 | 172.0 | 178.9 | 184.2 | 189.8 | 724.9 |
| revenue_growth | 10.0% | 9.1% | 8.3% | 7.7% | 7.1% | 5.0% | 5.0% | 33.2% | 4.0% | 4.0% | 3.0% | 3.0% | 18.3% | ||
| cogs | 40.0 | 44.0 | 47.0 | 51.0 | 182.0 | 55.0 | 58.0 | 59.9 | 62.8 | 235.7 | 63.6 | 66.2 | 66.3 | 68.3 | 264.5 |
| gross_profit | 60.0 | 66.0 | 73.0 | 79.0 | 278.0 | 85.0 | 92.0 | 97.7 | 102.5 | 377.2 | 108.4 | 112.7 | 117.9 | 121.4 | 460.4 |
| gross_margin | 60.0% | 60.0% | 60.8% | 60.8% | 60.4% | 60.7% | 61.3% | 62.0% | 62.0% | 61.5% | 63.0% | 63.0% | 64.0% | 64.0% | 63.5% |
| opex | 30.0 | 31.0 | 32.0 | 33.0 | 126.0 | 34.0 | 35.0 | 36.1 | 37.1 | 142.2 | 38.2 | 39.4 | 40.6 | 41.8 | 160.0 |
| operating_income | 30.0 | 35.0 | 41.0 | 46.0 | 152.0 | 51.0 | 57.0 | 61.6 | 65.4 | 235.0 | 70.1 | 73.3 | 77.3 | 79.7 | 300.4 |
| operating_margin | 30.0% | 31.8% | 34.2% | 35.4% | 33.0% | 36.4% | 38.0% | 39.1% | 39.5% | 38.3% | 40.8% | 41.0% | 42.0% | 42.0% | 41.4% |
1. Declare the model
{
"calendars": {
"fy": {
"granularity": "quarter", "start": "2025-Q1", "end": "2027-Q4", "rollups": ["year"],
"properties": { "frame": { "type": "string", "default": "hist" } },
"period_properties": { "2026-Q3..2027-Q4": { "frame": "fcst" } }
}
},
"modules": {
"is": {
"name": "Income statement",
"dims": ["time:fy"],
"line_items": {
"revenue": { "formulas": [
{ "scope": { "frame": "fcst" }, "formula": "PREV(revenue) * (1 + revenue_growth)" }
] },
"revenue_growth": { "formulas": [
{ "scope": { "frame": "hist" }, "formula": "IF PREV(revenue) = 0 THEN BLANK ELSE revenue / PREV(revenue) - 1" },
{ "scope": { "time": "level = year" }, "formula": "IF PREV(revenue) = 0 THEN BLANK ELSE revenue / PREV(revenue) - 1" }
] },
"cogs": { "formulas": [
{ "scope": { "frame": "fcst" }, "formula": "revenue * (1 - gross_margin)" }
] },
"gross_profit": { "formulas": [{ "formula": "revenue - cogs" }] },
"gross_margin": { "formulas": [
{ "scope": { "frame": "hist" }, "formula": "gross_profit / revenue" },
{ "scope": { "time": "level = year" }, "formula": "gross_profit / revenue" }
] },
"opex": { "formulas": [
{ "scope": { "frame": "fcst" }, "formula": "PREV(opex) * (1 + 3%)" }
] },
"operating_income": { "formulas": [{ "formula": "gross_profit - opex" }] },
"operating_margin": { "time_aggregation": "formula", "formulas": [{ "formula": "operating_income / revenue" }] }
}
}
},
"views": {
"income_statement": { "source": "is", "columns": ["time"], "line_item_axis": "rows", "header_props": { "time": ["frame"] }, "default": true }
}
}2. Land actuals and assumptions
[
{ "op": "set_values", "module": "is", "item": "revenue", "cells": [[["2025-Q1"], 100], [["2025-Q2"], 110], [["2025-Q3"], 120], [["2025-Q4"], 130], [["2026-Q1"], 140], [["2026-Q2"], 150]] },
{ "op": "set_values", "module": "is", "item": "cogs", "cells": [[["2025-Q1"], 40], [["2025-Q2"], 44], [["2025-Q3"], 47], [["2025-Q4"], 51], [["2026-Q1"], 55], [["2026-Q2"], 58]] },
{ "op": "set_values", "module": "is", "item": "opex", "cells": [[["2025-Q1"], 30], [["2025-Q2"], 31], [["2025-Q3"], 32], [["2025-Q4"], 33], [["2026-Q1"], 34], [["2026-Q2"], 35]] },
{ "op": "set_values", "module": "is", "item": "revenue_growth", "cells": [[["2026-Q3"], 0.05], [["2026-Q4"], 0.05], [["2027-Q1"], 0.04], [["2027-Q2"], 0.04], [["2027-Q3"], 0.03], [["2027-Q4"], 0.03]] },
{ "op": "set_values", "module": "is", "item": "gross_margin", "cells": [[["2026-Q3"], 0.62], [["2026-Q4"], 0.62], [["2027-Q1"], 0.63], [["2027-Q2"], 0.63], [["2027-Q3"], 0.64], [["2027-Q4"], 0.64]] }
]- One line item, two formulas. A rule carries a scope, and `{frame: hist}` and `{frame: fcst}` select the periods it governs. Where no rule matches, the cell is an input: that is how the actuals get in.
- `frame` is a property of the calendar, not a column of the model. Moving the boundary between actual and forecast is one call that sets it; every rule re-scopes itself.
- The drivers run backwards in history: growth and margin are computed from the actuals, and are yours to type in the forecast. The engine allows it because the two rules never govern the same period.
- The annual columns come from `rollups: [year]` on the calendar. Amounts sum. Ratios would be meaningless summed, so `revenue_growth` and `gross_margin` carry an explicit annual rule and `operating_margin` uses `time_aggregation: formula`, which re-evaluates it on the annual totals.
- Nothing here addresses a cell. Change one actual and only the cells that read it recompute; ask `explain` about any number and it names the rule, what it read and what depends on it.
Copy-pasteable: /examples/income-statement.md. Longer transcript, a CSV of actuals to a three-statement forecast in twelve tool calls: /examples/three-statement.md.
Why not a spreadsheet
| In a spreadsheet | In Finicast |
|---|---|
| =C7*(1+$B$3) | revenue = PREV(revenue) * (1 + growth) |
| =IF(D$2>$B$9, C7*(1+$B$3), D5) | amount {time: "> @last_actual"} = PREV(amount) * (1 + growth), and a separate rule for actuals |
| =SUMIFS(GL!$D:$D, GL!$A:$A, $A7, GL!$B:$B, D$2) recomputed for every cell | SUM(gl.amount MATCHING *) maintained as a group-by |
| A wrong reference produces a plausible number | An unknown name is a compile error with a suggestion |
| Changing one input recalculates the workbook | Only the affected cells are recomputed |
| No way to ask why a cell has its value | explain returns the rule, the reads and the dependents |
| Copy the file to try something | Add a scenario member; compare scenarios in one view |
| The agent reads cells to check its work | list_errors is empty or it is not |
| 100,000 customers means 100,000 rows of formulas | A list with 100,000 members and one rule |
| Colleagues expect an .xlsx | export kind=csv (xlsx coming) |
Quick start: local (open source)
npx finidb init ./model claude mcp add finidb -- npx finidb mcp --data ./model
Quick start: hosted
Create a project, copy the connect line from the Connect your agent card, and hand it to your agent.
What you get
lists · calendar · fact tables · modules · scoped rules · views · scenarios · explain · CSV export