K-φ for developers

Your finance data, one API.

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.

How to get access

Same path as everything else in K-φ: try, then your own data, then the plan.

Try it now
Developer sandbox

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.

Your own data
30-day free trial

Sign up, connect your ledger, banks or ERP, and create an API key on your own account.

Plan
Included

The API comes with the K-φ plan. No per-call pricing. When a trial ends unpaid, its keys stop with the account.

Get started

  1. Create a key

    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.

  2. Check who you are

    curl -s https://k-phi.com/api/v1/me \
      -H "Authorization: Bearer $KPHI_KEY"

    Returns the account, and the scopes this key carries.

  3. Read your first figures

    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" } }.

Google Sheets as an API

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
Try it with your sheet

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.

Load a sheet into K-φ

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

Webhooks: told when a sheet 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;

Data library

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.

Your variables

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.

Authentication and scopes

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.

Scopes a key can carry

ScopeWhat 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.

Endpoints

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…

Errors and limits

Errors

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.

Rate

Per address and per key, per minute.

Retries

On 429, wait the Retry-After seconds once. On 5xx, retry once after a short pause; requests are safe to repeat.

MCP: the same capabilities in Claude

Every API capability is also an MCP tool, with the same handler and the same figures.

Your account
Private MCP

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.

No account
Public connector

https://mcp.k-phi.com/mcp analyses a ledger file in a throwaway account. Documentation.

Your own tools
BUILD

Extensions built with Claude, Codex or Gemini CLI on the same API, inside K-φ, with their own keys and storage.

BUILD functions: your own logic, served as an API

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.

Worked example: margin calls for a commodity trader

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.

  1. The declaration, in the BUILD's kphi.json

    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" } } }
        }
      ]
    }
  2. A key that may call it

    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.

  3. The call

    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.

What K-φ guarantees on every call

CheckWhen 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 forwarded400 bad_input, with the field and the reason
The call reaches the BUILD's service signed by K-φ, and nothing else is accepted thereThe BUILD refuses an unsigned or stale call
The answer matches the declared output502 bad_output: a wrong answer never reaches the caller as data
The function refuses an input on its own terms422 function_refused, with its message
An answer within 30 s, 1 MB at most; the BUILD's rate and daily quota504 function_timeout, 429 rate_limited
Builder Mode on for the account, the BUILD switched on403 builder_disabled, or 404
Every call journaled; calls to a function that writes also in the audit trailAlways

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.

On the BUILD's side: verify the signature

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 */
Signed in
Your API Coming

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.

Public
Custom functions Coming

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.

Yours alone
Bought out

A function whose code the client bought out stays private: it is never published in Custom functions.

Ingest contract v1 Draft

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…

Check your rows before you send them

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"}]}'

Systems directory

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.

SystemConnectDataStatus
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.

Coming next

Ingest through the contract Coming

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.

Connection assistant Preview

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.

Webhooks Coming

K-φ calls your endpoint when something changes: a new GL version, a bank movement, a payment status.

Changelog

2026-10-29BUILD functions: the logic built in BUILD served at POST /api/v1/fn/<name>, with scopes run:build and run:build:write.
2026-10-29Ingest contract v1 (draft): eight objects, a JSON Schema each, and a dry-run validator.
2026-10-29Developer portal published. Scopes and limits added to openapi.json.
API v1Read statements, KPIs, cash, forecasts and scenarios; save scenarios; BUILD extension storage.