Statements, KPIs, cash positions, forecasts and scenarios, served by the same engine as the app: a figure you read here is the figure on screen. The tenant always comes from the key, every key carries scopes, and the API description below is generated from the engine, never written by hand.
Same path as everything else in K-φ: try, then your own data, then the plan.
No sign-up. A throwaway account with the Northwind demo group and a key that reads (and can serve a Google Sheet), valid 24 hours, then deleted.
Sign up, connect your ledger, banks or ERP, and create an API key on your own account.
The API comes with the K-φ plan. No per-call pricing. When a trial ends unpaid, its keys stop with the account.
In K-φ: Admin › AI › API keys (account owner). Pick the scopes the key needs, nothing more. The key is shown once: copy it into your secret store.
curl -s https://k-phi.com/api/v1/me \
-H "Authorization: Bearer $KPHI_KEY"
Returns the account, and the scopes this key carries.
curl -s https://k-phi.com/api/v1/list_versions -H "Authorization: Bearer $KPHI_KEY"
curl -s "https://k-phi.com/api/v1/get_pl?version=2026-09" -H "Authorization: Bearer $KPHI_KEY"
Every answer is { "data": …, "meta": … }. Every error is { "error": { "code", "message" } }.
Any Google Sheet, every tab served as JSON with your key: row 1 is the header, each row an object, numbers, dates and booleans typed. Read only: K-φ never writes to the sheet. Share it Anyone with the link · Viewer, register it once, then read it like any other endpoint.
curl -s https://k-phi.com/api/v1/sheets -H "Authorization: Bearer $KPHI_KEY" \
-H "Content-Type: application/json" -d '{"url":"https://docs.google.com/spreadsheets/d/…/edit"}'
curl -s https://k-phi.com/api/v1/sheets/sh_…/Loans -H "Authorization: Bearer $KPHI_KEY"
curl -s "https://k-phi.com/api/v1/sheets/sh_…/Loans?since=r_…" -H "Authorization: Bearer $KPHI_KEY" # { "changed": false } until the sheet changes
A key with read:sheets and write:sheets: a sandbox key (above) fills itself in.
Read again from Google when the last read is over a minute old. Up to 50 sheets per account, 50,000 rows per tab. Claude gets the same through the MCP connector: kphi_sheet_register, kphi_sheet_read.
A tab named after an object of the ingest contract (BAL, TRA, INV, MAP, FX; TRA - Main bank works too), with the contract's fields as its header row, is loaded into K-φ: balances and transactions to the bank accounts, invoices to AR/AP, mappings, FX rates. Every row of every such tab is checked first; one refused row and nothing is stored, the answer says which tab, line and field. The same ids again update, never duplicate. Storing needs write:data.
curl -s -X POST https://k-phi.com/api/v1/sheets/sh_…/validate -H "Authorization: Bearer $KPHI_KEY" # check only
curl -s -X POST https://k-phi.com/api/v1/sheets/sh_…/sync -H "Authorization: Bearer $KPHI_KEY" # check, then store
curl -s https://k-phi.com/api/v1/sheets -H "Authorization: Bearer $KPHI_KEY" -H "Content-Type: application/json" \
-d '{"url":"https://docs.google.com/spreadsheets/d/…/edit","auto_sync":true}' # load it each time it changes
Give an https URL; K-φ POSTs sheet.changed there when a registered sheet holds something else than at its last read, and sync.done or sync.failed after a load into K-φ. Sheets with a webhook are read again every 5 minutes, so the event comes even when nobody calls. A non-2xx answer is retried at 1 min, 5 min, 30 min, 2 h and 6 h; every attempt stays a week in the deliveries log.
curl -s https://k-phi.com/api/v1/webhooks -H "Authorization: Bearer $KPHI_KEY" \
-H "Content-Type: application/json" -d '{"url":"https://your.app/hooks/k-phi","events":["sheet.changed"]}'
# the answer gives the secret (whsec_…) once
curl -s -X POST https://k-phi.com/api/v1/webhooks/wh_…/test -H "Authorization: Bearer $KPHI_KEY" # a signed ping now
curl -s https://k-phi.com/api/v1/webhooks/wh_…/deliveries -H "Authorization: Bearer $KPHI_KEY" # what was sent, what came back
Each POST carries X-Kphi-Event, X-Kphi-Delivery and X-Kphi-Signature: t=<unix seconds>,v1=<hex>, the HMAC-SHA256 of <t>.<raw body> with your secret. Check it before trusting the body:
const [, t, v1] = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(req.headers['x-kphi-signature']);
const ok = crypto.timingSafeEqual(Buffer.from(v1, 'hex'),
crypto.createHmac('sha256', process.env.KPHI_WEBHOOK_SECRET).update(t + '.' + rawBody).digest())
&& Math.abs(Date.now() / 1000 - Number(t)) < 300;
Market, macro and public data served with your key: ECB reference rates and the euro yield curves, €STR, SOFR and the US Treasury curve, inflation and GDP per country. One catalogue, and every series carries its source, its licence and the attribution to keep when you show the data. Only open and attribution data is in the library: nothing you are not allowed to have.
curl -s https://k-phi.com/api/v1/data -H "Authorization: Bearer $KPHI_KEY" # the catalogue: id, unit, frequency, licence, last point
curl -s "https://k-phi.com/api/v1/data/ecb.fx.usd?from=2026-01-01" -H "Authorization: Bearer $KPHI_KEY" # a scalar: [{ date, value }]
curl -s "https://k-phi.com/api/v1/data/ecb.yc.aaa?key=10Y" -H "Authorization: Bearer $KPHI_KEY" # a curve: [{ date, key: "10Y", value }]
curl -s https://k-phi.com/api/v1/data/fred.ust/latest -H "Authorization: Bearer $KPHI_KEY" # the latest date: { "1M": 4.3, "2Y": 3.55, …, "30Y": 4.6 }
Scope read:data (in mcp:tools). A series is a scalar (one value a day), a curve (tenors: 3M, 1Y, 10Y) or a panel (ISO3 country codes). The answer's source.attribution is the text to show next to the data; source.terms_checked says when we last read the source's terms. Each series is read from its source daily; the state of the feed and the date of the last point are in the catalogue. Claude gets the same through the MCP connector: kphi_data_catalog, kphi_data_series.
Any reference series of your own (a price, a curve, an index, a margin rate, a volume) is a variable: it reads as var.<CODE> beside the public series, with the same calls, and it never leaves your account. A point is a date, a key (empty for one value a day, a tenor such as 3M or 10Y for a curve) and a value. Exchange prices, index levels and vendor curves are licensed to you, not to us: this is how they come in.
curl -s -X POST https://k-phi.com/api/v1/data/var -H "Authorization: Bearer $KPHI_KEY" -H "Content-Type: application/json" \
-d '{"code":"BRENT","name":"Brent crude","unit":"USD/bbl","frequency":"daily"}'
curl -s -X PUT https://k-phi.com/api/v1/data/var.BRENT -H "Authorization: Bearer $KPHI_KEY" -H "Content-Type: application/json" \
-d '{"points":[{"date":"2026-11-03","value":64.2}]}' # all or none; the same date and key again updates
curl -s -X PUT https://k-phi.com/api/v1/data/var.EUR_SWAP -H "Authorization: Bearer $KPHI_KEY" -H "Content-Type: application/json" \
-d '{"points":[{"date":"2026-11-03","key":"2Y","value":2.43},{"date":"2026-11-03","key":"10Y","value":2.82}]}'
curl -s https://k-phi.com/api/v1/data/var.EUR_SWAP/latest -H "Authorization: Bearer $KPHI_KEY" # { "2Y": 2.43, "10Y": 2.82 }
Scopes: read:data to read, write:vars (or write:data, which includes it) to define, load and remove (DELETE /api/v1/data/var.BRENT). A variable named for the first time by a load is created. From a sheet or a file: a tab named VAR with the columns variable, date, key, value, or a tab VAR - BRENT with date, value (or date, 3M, 1Y, 10Y: one column per key); every line is checked first, nothing is stored until all pass. A webhook with the event var.changed is told each time a variable gets new or changed points. Claude: kphi_var_define, kphi_var_put, kphi_var_remove; in the app, /connect variable in the Copilot.
Send the key as a bearer token. The account is taken from the key, never from a parameter, so a key can only ever reach its own data. Calls come from a server: a browser call needs an extension credential (BUILD).
Authorization: Bearer kphi_…
Exception: the GL ingest endpoints, used by ERP connectors, take the key in an X-API-Key header and need the gl:ingest scope.
| Scope | What it allows |
|---|---|
| Loading from openapi.json… | |
Read by default. A capability that names no scope is refused. Write scopes on an account with several people may need a second approval.
Generated from /api/v1/openapi.json, the live description of the engine. Read capabilities answer GET with query parameters or POST with a JSON body; write capabilities answer POST only.
Loading from openapi.json…
401 no or invalid key · 403 scope or extension refused · 422 input refused by the capability · 429 rate or daily quota reached, with Retry-After.
Per address and per key, per minute.
On 429, wait the Retry-After seconds once. On 5xx, retry once after a short pause; requests are safe to repeat.
Every API capability is also an MCP tool, with the same handler and the same figures.
Add the custom connector https://k-phi.com/mcp in Claude, sign in with OAuth, or use an API key. Same scopes as the API.
https://mcp.k-phi.com/mcp analyses a ledger file in a throwaway account. Documentation.
Extensions built with Claude, Codex or Gemini CLI on the same API, inside K-φ, with their own keys and storage.
A rule your team describes to Claude in BUILD becomes a function: declared, checked, signed and served at POST /api/v1/fn/<name>, beside K-φ's own API. It runs on your account's data, it is called with your keys, and every call is in your BUILD's journal.
The treasurer asks Claude, in BUILD: "From our open futures positions and a price shock, give me the variation and initial margin we will have to post each day for the next two weeks, and put it in the cash forecast." Claude writes the logic and declares two functions: one that computes, one that books the result in CASH. The risk system calls the first every evening; the treasurer books with the second.
Written by Claude with the Builder pass. K-φ checks it at deploy: a name it can serve, a path under /fn/, schemas it can check, an example that fits them. A wrong one refuses the deploy.
{
"menu": [ { "label": "Margin calls", "path": "/app#margin" } ],
"functions": [
{
"name": "margin_call",
"summary": "Daily variation and initial margin to post, per exchange, for a price shock",
"input": {
"type": "object",
"properties": {
"as_of": { "type": "string", "description": "Valuation date, YYYY-MM-DD" },
"shock_pct": { "type": "number", "minimum": -50, "maximum": 50 },
"days": { "type": "integer", "minimum": 1, "maximum": 30 }
},
"required": ["as_of", "shock_pct"],
"additionalProperties": false
},
"output": {
"type": "object",
"properties": {
"currency": { "type": "string" },
"rows": { "type": "array", "items": { "type": "object",
"properties": { "date": { "type": "string" }, "exchange": { "type": "string" },
"variation": { "type": "number" }, "initial": { "type": "number" } },
"required": ["date", "exchange", "variation", "initial"] } },
"peak_need": { "type": "number" }
},
"required": ["currency", "rows", "peak_need"]
},
"example": {
"input": { "as_of": "2026-11-02", "shock_pct": -10, "days": 10 },
"output": { "currency": "USD", "peak_need": 4200000,
"rows": [ { "date": "2026-11-03", "exchange": "ICE", "variation": 1850000, "initial": 600000 } ] }
}
},
{
"name": "book_margin_calls",
"summary": "Put the margin calls of a run in CASH > Cash forecasts, replacing the previous run",
"writes": true,
"input": { "type": "object", "properties": { "as_of": { "type": "string" }, "shock_pct": { "type": "number" } }, "required": ["as_of"] },
"output": { "type": "object", "properties": { "lines": { "type": "integer" } } }
}
]
}In K-φ, Settings > API keys: a key with run:build calls functions that only read. A function declared "writes": true also needs run:build:write, which an admin grants on purpose. The key reaches its own account's functions only.
The body is the input (or { "input": ... }). The answer is the function's output in data.
curl -s https://k-phi.com/api/v1/fn/margin_call \
-H "Authorization: Bearer $KPHI_KEY" -H "Content-Type: application/json" \
-d '{ "as_of": "2026-11-02", "shock_pct": -10, "days": 10 }'
{ "data": { "currency": "USD", "peak_need": 4200000, "rows": [ ... ] },
"meta": { "api": "v1", "function": "margin_call", "extension": "Margin desk",
"call_id": "fc_…", "ms": 412, "at": "2026-11-02T18:00:03Z" } }
GET /api/v1/fn lists the functions a key may call, with their schemas and examples.
| Check | When it fails |
|---|---|
The key belongs to the account and holds run:build (run:build:write for a function that writes) | 403 insufficient_scope; another account's function does not exist for it (404) |
| The input matches the declared schema, before anything is forwarded | 400 bad_input, with the field and the reason |
| The call reaches the BUILD's service signed by K-φ, and nothing else is accepted there | The BUILD refuses an unsigned or stale call |
| The answer matches the declared output | 502 bad_output: a wrong answer never reaches the caller as data |
| The function refuses an input on its own terms | 422 function_refused, with its message |
| An answer within 30 s, 1 MB at most; the BUILD's rate and daily quota | 504 function_timeout, 429 rate_limited |
| Builder Mode on for the account, the BUILD switched on | 403 builder_disabled, or 404 |
| Every call journaled; calls to a function that writes also in the audit trail | Always |
Schemas use a subset of JSON Schema that K-φ checks exactly: type, properties, required, additionalProperties, items, enum, minimum, maximum, maxLength, maxItems, description, title, format (informative). Anything else is refused at deploy rather than silently ignored.
K-φ posts { call_id, function, input, caller, at } to the function's path with a KPHI-Signature: t=<unix seconds>,v1=<hex> header: an HMAC-SHA256 of t + "." + body with the BUILD's own secret, which K-φ places in its service as KPHI_FN_SECRET. The function's address is public; the signature is what proves K-φ sent the call. The BUILD template does this for you; the check, in full:
const crypto = require('crypto');
function fromKphi(header, rawBody) {
const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header || '');
if (!m || Math.abs(Date.now() / 1000 - Number(m[1])) > 300) return false;
const want = crypto.createHmac('sha256', process.env.KPHI_FN_SECRET).update(m[1] + '.' + rawBody).digest();
const got = Buffer.from(m[2], 'hex');
return got.length === want.length && crypto.timingSafeEqual(got, want);
}
/* answer { "output": ... }, or HTTP 422 { "error": { "message": "..." } } to refuse an input */
This page, when you are signed in: your account's functions, generated from their declarations, with what they take and return, the example, a Try it box with one of your keys, and ready-to-paste calls for curl, Google Sheets and Excel. Nobody else sees them.
A catalogue of functions built in BUILD, published anonymised: no client name, no counterparty, no figure from a client's contracts, and an example with neutral data. Each one can be installed in your own BUILD and runs on your own data, never on anyone else's.
A function whose code the client bought out stays private: it is never published in Custom functions.
One shape per kind of financial record, whatever system it comes from. A connection maps its source fields to these objects; a file, an API call or a sync then lands the same way. Rendered from /api/public/contract/v1; each object also has a JSON Schema.
Loading the contract…
A dry run: nothing is stored. The answer says, row by row, which field is wrong and why. Up to 5,000 rows a call, 30 calls a minute.
curl -s https://k-phi.com/api/public/contract/v1/validate \
-H "Content-Type: application/json" -H "X-Requested-With: XMLHttpRequest" \
-d '{"object":"INV","rows":[{"id":"INV-1042","direction":"AR","counterparty_name":"Acme","currency":"USD","amount":1200,"due_date":"2026-11-30"}]}'
What K-φ connects to, how each one authenticates, what the client has to provide and what maps to what. Generated from the connection recipes K-φ runs; the vendors' own documentation is linked, never copied.
| System | Connect | Data | Status |
|---|---|---|---|
| Loading… | |||
Built in: a K-φ connector, one click. Verified: a real connection succeeded with this recipe. From the vendor docs: written from the public documentation, not yet proven on a live account.
Today the contract validates. Next, the same rows are stored: from a connection's sync, a file, or a call to the API, with the source and time of every row kept.
In the app, "Connect Salesforce" opens an assistant that asks for what is missing, says where to find it, tests each answer and proposes the mapping. Secrets go straight to the vault, never into the chat.
K-φ calls your endpoint when something changes: a new GL version, a bank movement, a payment status.
| 2026-10-29 | BUILD functions: the logic built in BUILD served at POST /api/v1/fn/<name>, with scopes run:build and run:build:write. |
| 2026-10-29 | Ingest contract v1 (draft): eight objects, a JSON Schema each, and a dry-run validator. |
| 2026-10-29 | Developer portal published. Scopes and limits added to openapi.json. |
| API v1 | Read statements, KPIs, cash, forecasts and scenarios; save scenarios; BUILD extension storage. |