# Layerz — Full Reference > Layerz is the API-first financial projection engine. It separates business logic (versioned, structured code) from data (editable Excel), enabling AI agents to programmatically build, compute, and export audit-ready financial models. Think "Figma for financial models" — design the structure once, generate perfect Excel workbooks on demand. ## What Layerz Does Layerz lets you create structured financial meta-models where every assumption is explicit, every formula is transparent, and every output is traceable. Models are built from typed items (assumptions, formulas, layers, balances) organized in a section-first hierarchy. The compute engine evaluates all items in dependency order, and the export pipeline generates self-contained Excel workbooks with live formulas, editable assumption cells, and proper financial formatting. Unlike spreadsheets, Layerz models never break when you change structure. Add a timeline, insert an item, modify a formula — the engine re-resolves all dependencies automatically. ## Use Cases - **SaaS Revenue Model** — MRR, churn rate, expansion revenue, unit economics, LTV/CAC - **Startup Runway** — Burn rate, funding rounds, hiring plan, cash runway milestones - **LBO Analysis** — Debt tranches, leverage ratios, interest schedules, equity returns, IRR - **Manufacturing P&L** — Raw materials, labor costs, production capacity, gross margins, capex - **Consulting Agency** — Utilization rates, day rates, headcount planning, project pipeline - **Infrastructure Project** — Capex schedule, depreciation, operating cash flows, IRR/NPV - **Fintech Lending** — Loan book growth, default rates, net interest margin, provisions - **Real Estate Development** — Construction costs, rental income, vacancy rates, cap rates - **E-commerce** — GMV, conversion rates, average order value, fulfillment costs, contribution margin ## Why AI Agents Use Layerz - **Deterministic & Auditable** — Every formula is transparent. Results are reproducible. - **Structured, Not Flat** — Typed items with explicit dependencies, not cell references. - **Compute on Demand** — One API call evaluates the entire model in dependency order. - **Open Excel Output** — Export audit-ready .xlsx with live formulas. No vendor lock-in. - **Version-Controlled Logic** — Model structure is code. Version it, diff it, branch it. --- # Agent API v1 REST API for AI agents to create, read, update, and compute Layerz financial models. ## Model Concepts A Layerz model is a structured financial plan. Understanding these concepts is essential to build coherent models via the API. **Model** — a named container with timelines and a flat ordered array of items. Created with `POST /models`, it comes pre-loaded with default sections (P&L, Cash flow, Balance sheet). The model follows a **section-first architecture**: only sections exist at root level. Standalone layers/balances are auto-wrapped in sections, and orphan items are collected into an `__unsorted__` section. **Timelines** — define the time axis. Four canonical types: `constant` (single value), `monthly`, `yearly`, `quarterly`. Each dated timeline has a `start` and `end` (YYYY-MM-DD). An item's `timeline_ref` determines how many values it holds (one per period). **Items** — the building blocks. Every item has a `uid` (auto-generated 8-char ID), a `role`, a `display_name`, and an optional `timeline_ref`. Six roles: `assumption` (raw input), `formula` (computed expression with lags and functions), `callup` (copies from source), `layer` (container with children, always sums), `balance` (cumulative position: `Closing[t] = Opening[t] + Σ children`), `section` (pure structural container — no timeline, no values, cannot be referenced). **Parent containers** — pass `parent_layer_uid` on create to attach an item as a child of a section, layer, or balance. **Compute** — evaluates all items in dependency order (topological sort). `/compute` returns values indexed by UID, with optional `?items=` filter. **Typical workflow**: Create model → create sections → add layers/items into sections → add formulas → compute → iterate. ## Authentication All requests require a Bearer token: ``` Authorization: Bearer ``` API keys are SHA-256 hashed in the `api_keys` table. Each key is scoped to a user — queries are filtered by `user_id` so agents only access their own models. ### Creating an API key 1. Click your avatar (top-right corner) → **Layerz for Agents** 2. Enter a name for your key and select an optional expiration 3. Click **Generate key** 4. Copy the key immediately — it will not be shown again The key format is `lz_<40 hex characters>`. Store it securely (e.g., in environment variables or a secrets manager). ## Rate Limiting 120 requests per minute per IP address. ## Endpoints ### Models | Method | Path | Description | |--------|------|-------------| | GET | `/api/v1/models` | List all accessible models | | POST | `/api/v1/models` | Create a new model (with default layers) | | GET | `/api/v1/models/:id` | Get model detail: items, timelines, lists | | PATCH | `/api/v1/models/:id` | Update model name | | DELETE | `/api/v1/models/:id` | Delete a model | | POST | `/api/v1/models/:id/duplicate` | Duplicate a model (optional `{ "name": "..." }`) | ### Snapshot (CLI sync) | Method | Path | Description | |--------|------|-------------| | GET | `/api/v1/models/:id/snapshot` | Download full StoredModel. Returns `{ manifest, schema, inputs }` + `ETag` header (sha256 hash) | | PUT | `/api/v1/models/:id/snapshot` | Upload full StoredModel. Optional `If-Match` header for optimistic concurrency. Returns `{ hash, updated_at }` + `ETag`. Returns 409 on hash mismatch | ### Versions (history) | Method | Path | Description | |--------|------|-------------| | GET | `/api/v1/models/:id/versions` | List version history (Pro only, retention-filtered). Returns `{ versions: VersionSummary[] }` | | POST | `/api/v1/models/:id/versions` | Create version from current state. Body: `{ trigger_type }`. Skips if hash unchanged. | | GET | `/api/v1/models/:id/versions/:vid` | Get full version data (manifest, schema, inputs) | | PATCH | `/api/v1/models/:id/versions/:vid` | Rename version. Body: `{ label: string \| null }` | | POST | `/api/v1/models/:id/versions/:vid` | Restore version (saves current state first) | | GET | `/api/v1/models/:id/versions/:vid/diff` | Semantic diff between version and current model | ### Items | Method | Path | Description | |--------|------|-------------| | GET | `/api/v1/models/:id/items` | List all items | | POST | `/api/v1/models/:id/items` | Add item (any role) | | POST | `/api/v1/models/:id/items/batch` | Add multiple items in one call (max 100) | | GET | `/api/v1/models/:id/items/:uid` | Get single item | | PATCH | `/api/v1/models/:id/items/:uid` | Update item fields | | DELETE | `/api/v1/models/:id/items/:uid` | Remove item (also cleans layer refs) | | PATCH | `/api/v1/models/:id/items/batch-update` | Update multiple items in one call (max 100) | | POST | `/api/v1/models/:id/items/batch-delete` | Delete multiple items in one call (max 100) | | POST | `/api/v1/models/:id/items/reorder` | Reorder items (pass ordered UID array; optional `layer_uid` for partial reorder) | ### Lists | Method | Path | Description | |--------|------|-------------| | GET | `/api/v1/models/:id/lists` | List all lists | | POST | `/api/v1/models/:id/lists` | Create a list with items | | GET | `/api/v1/models/:id/lists/:listId` | Get single list | | PATCH | `/api/v1/models/:id/lists/:listId` | Update list name and/or items | | DELETE | `/api/v1/models/:id/lists/:listId` | Delete list (clears `liste_ref` on items) | ### Import | Method | Path | Description | |--------|------|-------------| | POST | `/api/v1/models/:id/import/analyze` | Analyze file structure before import | | POST | `/api/v1/models/:id/import` | Import historical data (FEC, Excel, CSV) | ### Mapping Sets | Method | Path | Description | |--------|------|-------------| | GET | `/api/v1/mapping-sets` | List mapping sets (system + user) | | POST | `/api/v1/mapping-sets` | Create a new mapping set | | GET | `/api/v1/mapping-sets/:id` | Get set detail with mappings | | POST | `/api/v1/mapping-sets/:id/mappings` | Add a mapping rule to a set | | DELETE | `/api/v1/mapping-sets/:id` | Delete a mapping set (non-system only) | ### Compute | Method | Path | Description | |--------|------|-------------| | GET | `/api/v1/models/:id/compute` | Evaluate all items, return values per UID. Optional `?items=uid1,uid2` to filter response | ## Request / Response Examples ### Models `POST /models`: `{ "name": "Plan 2026", "timelines": { "yearly": { "start": "2025-01-01", "end": "2030-12-31" } } }` — `timelines` optional, defaults to yearly. `PATCH /models/:id`: `{ "name": "New Name" }`. `POST /models/:id/duplicate`: `{ "name": "Copy" }` (name optional). ### Items — Add (POST /models/:id/items) ```json // Assumption { "role": "assumption", "display_name": "Revenue", "timeline_ref": "yearly", "values": [100, 120, 140] } // Formula (with lag) { "role": "formula", "display_name": "Interest", "formula": "debt_Y-1 * rate", "inputs": ["debt", "rate"] } // Layer (always computes sum of children) { "role": "layer", "display_name": "Revenue", "children": [{ "uid": "" }] } // Chart layer (layer displayed as a chart in the UI and Excel export) { "role": "layer", "display_name": "Revenue Chart", "display_as": "chart", "chart_config": { "type": "line" }, "children": [{ "uid": "" }] } // Section (structural container, no computation) { "role": "section", "display_name": "Assumptions", "children": [{ "uid": "" }] } // Balance { "role": "balance", "display_name": "Debt", "opening_balance": 10000, "children": [{ "uid": "" }] } // List-mode assumption { "role": "assumption", "display_name": "Salary", "liste_ref": "", "timeline_values": { "": [5000], "": [6000] } } // Attach to layer on create { "role": "assumption", "display_name": "Subs", "parent_layer_uid": "", "value": 50 } ``` ### Batch operations **Batch create** (`POST /items/batch`): items can include `temp_id` to reference items created earlier in the same batch. Server resolves `temp_id` in `inputs`, `source_uid`, `children[].uid`, `parent_layer_uid`, `formula`. ```json { "items": [ { "temp_id": "rev", "role": "assumption", "display_name": "Revenue", "values": [100, 120] }, { "role": "formula", "display_name": "Tax", "formula": "MAX(0, rev) * 0.25", "inputs": ["rev"] } ]} ``` **Batch update** (`PATCH /items/batch-update`): `{ "updates": [{ "uid": "", "patch": { "value": 42 } }] }` **Batch delete** (`POST /items/batch-delete`): `{ "uids": ["", ""] }` → `{ "deleted_count": 2 }` **Reorder** (`POST /items/reorder`): `{ "uids": ["", ""] }`. Partial: add `"layer_uid": ""` to reorder only that layer's children. **Update item** (`PATCH /items/:uid`): `{ "display_name": "New", "values": [200, 220] }`. `role` can be changed; UID preserved. ### Lists `POST /lists`: `{ "display_name": "Depts", "items": [{ "label": "Prod" }, { "label": "Sales", "mapped_to": { "": [""] } }] }`. `PATCH /lists/:id`: update `display_name` and/or items by uid. ### Compute (GET /models/:id/compute) Optional `?items=uid1,uid2` to filter response. Returns: ```json { "values": { "": [100, 120, 140] }, "errors": { "": "Circular dependency" }, "order": ["", ""], "periods": { "yearly": ["2025", "2026", "2027"], "constant": ["constant"] }, "item_timelines": { "": "yearly", "": "constant" } } ``` ### Import (multipart/form-data) **Analyze** (`POST /import/analyze`): `file` + optional `sheetName`. Returns structure detection + `row_labels`. **Import** (`POST /import`): `file` + `timelineId` + `structure` (JSON) + `mappings` (JSON) + `sheetName`. FEC auto-detects (no structure/mappings). Workflow: analyze → map → import. ### Mapping Sets `POST /mapping-sets`: `{ "name": "My PCG" }`. `POST /mapping-sets/:id/mappings`: `{ "account_pattern": "60*", "target_metric": "Achats", "sign_convention": "natural" }`. ## Item Fields Reference **Required on create**: `role` (`assumption|formula|callup|layer|balance|section`), `display_name`. **Common**: `timeline_ref` (`constant|monthly|yearly|quarterly`), `format`, `aggregation` (`sum|average|stock`), `item_style` (`subtotal|total|kpi`), `parent_layer_uid` (create only). **Assumption**: `value` (constant), `values` (array per period), `liste_ref` + `timeline_values` (list mode). **Formula**: `formula` (expression with UIDs, lags, functions), `inputs` (all referenced UIDs). **Callup**: `source_uid`. Timeline auto-inferred. `liste_ref` for list-mode callup. **Layer/Balance/Section**: `children` (`[{uid}]`), `opening_balance` (balance only). Sections have no timeline, no values, no computation. **Chart layers**: `display_as` (`'chart'`), `chart_config` (`{ type?: 'line' | 'bar' | 'area', title?: string }`). A chart layer is a regular layer with `display_as: 'chart'` — it computes like a layer but renders as a chart in the UI and Excel export. ## Formula Functions `MAX(a,b)`, `MIN(a,b)`, `IF(cond,t,f)`, `ABS(x)`, `POW(b,e)`, `IRR(cashflows)` (scalar), `NPV(rate,cf)`, `PMT(rate,nper,pv)`, `IPMT(rate,per,nper,pv)`, `PPMT(rate,per,nper,pv)`, `AND(a,b,...)`, `OR(a,b,...)`, `NOT(x)`, `EDATE(serial,months)`, `IN_PERIOD(serial,start,end)`. ## Error Responses All errors: `{ "error": "" }`. Status codes: 400 (validation), 401 (auth), 404 (not found), 429 (rate limit), 500 (server). ## Timeline Alignment When a formula references items on different timelines, values are automatically projected: | Source → Target | Behavior (sum) | Behavior (stock) | |----------------|----------------|-------------------| | Constant → any | Broadcast: value repeated for every period | Same | | Yearly → Monthly | Yearly value / month count per year | 0 (no meaning) | | Monthly → Yearly | Sum of monthly values in the year | Last month's value | | Quarterly → Yearly | Sum of quarters | Last quarter | Aggregation is controlled by `aggregation` on the source item: `sum` (default), `average`, `stock`. Balance items default to `stock`. ## Lag Edge Cases - `uid_Y-1` at **period 0** returns `0` (no prior data). For balance items, `balance_Y-1` at period 0 returns `opening_balance`. - **Lag unit conversion**: `_Y-1` on monthly timeline = 12-period lookback. `_Q-1` on monthly = 3-period lookback. `_Y-1` on quarterly = 4-period lookback. - **Self-referencing lag** is allowed: `A = B + A_M-1` is valid (the lag breaks the cycle). Zero-lag self-references are detected as circular dependencies. ## Formula Operator Precedence Standard math precedence: `*` `/` bind tighter than `+` `-`. Parentheses override. Left-to-right associativity. Unary minus is supported: `-uid` evaluates to `0 - uid`. --- # Layerz CLI Agent-friendly CLI for building and managing financial models locally, with push/pull sync to the Layerz server. ## Build ```bash npm run build:cli # Compiles core library + bundles CLI → cli/dist/layerz.js ``` ## Authentication Configure via env vars (preferred for agents/CI) or `layerz auth set`: ```bash # Env vars (highest priority) export LAYERZ_API_KEY=lz_... export LAYERZ_SERVER_URL=https://app.layerz.cc # Or persistent config (~/.layerz/config.json) layerz auth set --key lz_... --server https://app.layerz.cc layerz auth status # Show current config ``` ## Commands ### `layerz create ` Creates a new model locally (`.layerz.json` in cwd). Includes default sections (P&L, Cash flow, Balance sheet) and yearly timeline 2024-2028. ```bash layerz create "Business Plan 2026" layerz create "Another" --force # Overwrite existing ``` ### `layerz add-item` Adds an item to the local model. Supports all roles: assumption, formula, callup, layer, balance, section. ```bash # Add assumption with monthly values layerz add-item --role assumption --name "Revenue" --timeline yearly --values '[100,200,300,400,500]' # Add assumption with constant value layerz add-item --role assumption --name "Tax Rate" --timeline constant --value 0.25 --format percent # Add formula layerz add-item --role formula --name "Net Revenue" --formula 'rev1 * (1 - tax1)' --inputs '["rev1","tax1"]' # Add callup layerz add-item --role callup --name "Revenue Copy" --source rev1 # Add layer with children and balance mode layerz add-item --role layer --name "Cash" --timeline yearly --balance --opening-balance 1000 # Add section (structural container, no computation) layerz add-item --role section --name "Assumptions" # Add chart layer layerz add-item --role layer --name "Revenue Chart" --chart-type line --chart-title "Revenue Over Time" # Add item as child of an existing container layerz add-item --role assumption --name "Cost" --parent "P&L" --values '[-50,-60,-70]' ``` **Options:** | Flag | Description | |------|-------------| | `--role ` | Required. `assumption`, `formula`, `callup`, `layer`, `balance`, or `section` | | `--name ` | Required. Display name | | `--timeline ` | Timeline ref: `monthly`, `yearly`, `constant` (default: `monthly`) | | `--values ` | Values as JSON array (assumptions) | | `--value ` | Single scalar value (assumptions) | | `--formula ` | Formula expression (formulas) | | `--inputs ` | Input UIDs as JSON array (formulas) | | `--source ` | Source UID (callups) | | `--parent ` | Parent container UID or display name | | `--format ` | Format name (e.g. `percent`, `integer`) | | `--balance` | Mark layer as balance layer | | `--opening-balance ` | Opening balance for balance layers | | `--chart-type ` | Chart type: `line`, `bar`, `area` | | `--chart-title ` | Chart title | | `--json` | Output result as JSON | ### `layerz validate [file]` Validates model structure. Exit code 0 = valid, 1 = errors. ```bash layerz validate layerz validate path/to/model.layerz.json layerz validate --json ``` ### `layerz compute` Computes all model values locally using the compute engine. ```bash layerz compute # Item count + error summary layerz compute --summary # Formatted table of key items layerz compute --json # Full JSON output layerz compute --output results.json ``` ### `layerz export` Generates an Excel workbook from the local model. ```bash layerz export # → model.xlsx layerz export --output plan.xlsx # Custom path ``` ### `layerz pull` Downloads a model snapshot from the server. ```bash layerz pull --id <model-uuid> # First pull layerz pull # Subsequent pulls (reads ID from meta) ``` ### `layerz diff` Compares the local model with the remote snapshot. ```bash layerz diff layerz diff --id <uuid> ``` ### `layerz push` Uploads the local model to the server with optimistic concurrency. ```bash layerz push # Uses If-Match header layerz push --force # Last-write-wins layerz push --id <uuid> # Explicit model ID ``` ## Workflow ```bash # Local-only workflow layerz create "My Model" layerz add-item --role assumption --name "Revenue" --timeline yearly --values '[100,200,300]' layerz add-item --role formula --name "Tax" --formula 'rev1 * 0.25' --inputs '["rev1"]' layerz validate layerz compute --json layerz export --output plan.xlsx # Server sync workflow layerz pull --id abc-123 # ... modify locally layerz push ``` ## File Format - `.layerz.json` — StoredModel (`{ manifest, schema, inputs }`) - `.layerz.meta.json` — Sync metadata: `{ server_url, model_id, hash, updated_at }` ## Exit Codes | Code | Meaning | |------|---------| | 0 | Success | | 1 | Validation error or general failure | | 2 | Conflict (409 — model modified on server) |