Skip to content
Contents

GirovoAPI

With Girovo API your app works with Girovo on the same computer. It reads accounts and bookings, changes categories, notes, budgets and settings, and proposes transfers. Every app asks the user first.

For
Girovo Next 7.0, Windows and Linux
Connection
only on this computer
Default
off
Protocol
JSON-RPC 2.0, one message per line

Girovo API belongs to Girovo Next, the preview of Girovo 7. Methods and answers may still change until version 7.0.

The essentials

  • Girovo API is off until the user switches it on: Einstellungen › Girovo API › Girovo API zulassen.
  • There is no network and no HTTP. On Windows it is a named pipe, on Linux a Unix socket. Only the signed in user can reach it.
  • Every app asks first. Girovo shows a dialog with the app's name, its publisher and each permission on its own.
  • Girovo reads the publisher from your app's code signature. What the app says about itself does not count for this. Unsigned apps show as "Herausgeber unbekannt" (publisher unknown).
  • Your app only proposes transfers. Girovo opens them filled in, the user checks and sends them, then the bank asks for its approval.
  • The user can take back any permission in Einstellungen › Girovo API, for each app on its own.

Connecting

Open the pipe or the socket and send JSON-RPC 2.0, one message per line, ending in \n. Answers and events come back the same way.

Address

Windows\\.\pipe\girovo-api-<SID>The user's SID, for example from whoami /user.
Linux$XDG_RUNTIME_DIR/girovo-api.sockWithout XDG_RUNTIME_DIR the socket is in ~/.config/Girovo.

The first call: girovo.hello

Every connection starts with girovo.hello. Before it, Girovo answers nothing else. You give your app's name, the permissions you need and a short reason for the user.

RequestJSON
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "girovo.hello",
  "params": {
    "app": "My Household Book",
    "version": "1.0",
    "permissions": [
      "accounts.read",
      "transactions.read"
    ],
    "reason": "Shows your balances in the household book."
  }
}

The answer comes once the user has decided. granted lists every permission your app has now. The user can switch single permissions off, so check granted.

ResponseJSON
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "granted": [
      "accounts.read",
      "transactions.read"
    ],
    "girovo": {
      "version": "7.0.0",
      "api": 1
    }
  }
}

Wrapped here for reading. On the wire every message is a single line.

What the dialog shows

  • Your app's icon and name, from its executable, not from hello.
  • "Herausgeber: <name>" with a check mark when Windows trusts the signature. Otherwise "Herausgeber: unbekannt" as a warning, with the file's path.
  • Each permission as its own switch. The user can turn single ones off.
  • Your reason from hello, marked as the app's words.
  • Zulassen (allow) and Ablehnen (refuse).

What can happen

AllowedGirovo remembers it until the user takes it back. A later hello asks only for permissions that are missing.
Refusedrefused_by_user. After that your app gets denied until Girovo restarts.
No answertimeout after 2 minutes. This is not a refusal, you may ask again.
Not signed inThe call waits. Girovo comes to the front and says at the login which app asks. After 2 minutes without a login you get locked.
Too much at oncebusy, for example with more than 4 connections from one program.

Permissions

Ask only for what your app really needs. If a permission is missing later, you get not_permitted with data.permission. A new hello with that permission opens the dialog just for it.

PermissionAllows
accounts.readRead accounts, balances, available amounts and the total balance
transactions.readRead and search bookings with notes and categories
insights.readRead the Analyse figures, contracts and subscriptions, budgets and savings goals
askAsk Girovo AI (no method yet)
categories.writeCategories of bookings, rules and own categories
notes.writeNotes on bookings
budgets.writeMonthly budgets, savings goals, reminders and contract names
settings.readRead settings
settings.writeChange settings, only the allowed ones
bank.refreshFetch new data from the bank. If the bank asks for an approval, the user gives it in Girovo.
transfers.requestPropose transfers and scheduled transfers. The user checks and sends each one in Girovo.

Methods

All methods start with girovo. Amounts are whole cents with a currency, such as {"cents":1299,"currency":"EUR"}. Days are yyyy-mm-dd, months yyyy-mm. Accounts, bookings and contracts have their own IDs from the read methods.

Reading

girovo.accounts.listPermission: accounts.read
{}

{ accounts[], total[] }: each account with id, name, type, iban, bank, balance and available, plus the total balance per currency.

girovo.transactions.listPermission: transactions.read
{ account?, from?, to?, query?, category?, limit?, cursor? }

{ transactions[], next? }: bookings, newest first. limit 1 to 500, default 100. next is the cursor for the next page.

girovo.insights.monthPermission: insights.read
{ month? }

Income, spending, net and both by category for one month, by default this month. coverage says whether the month is fully loaded.

girovo.contracts.listPermission: insights.read
{}

{ contracts[] }: contracts and subscriptions with the amount per month and year, last and next day.

girovo.budgets.listPermission: insights.read
{ month? }

{ month, coverage, budgets[] }: each monthly budget with spending, rest and state (left, exact, over, unknown).

girovo.goals.listPermission: insights.read
{}

{ goals[] }: savings goals with target, balance and what is still missing.

Changing

girovo.categories.setPermission: categories.write
{ booking, category | null }

{ booking, category }. null puts the booking back to its rule or the suggestion.

girovo.rules.addPermission: categories.write
{ booking, category } | { payee, category }

{ applied, payees[], … }. If the user filed the payee by hand, that stays: applied false, reason filed_by_hand.

girovo.categories.createPermission: categories.write
{ name, direction?: "out" | "in" }

{ category: { id, name }, direction }

girovo.notes.setPermission: notes.write
{ booking, text | null }

{ booking, note }. One line. null or empty removes the note.

girovo.budgets.setPermission: budgets.write
{ category, cents | null }

{ category, cents }. Spending categories only. null removes the budget.

girovo.goals.setPermission: budgets.write
{ account, name, target | null, by? }

{ account, goal }. Savings accounts only, by as yyyy-mm. Where savings goals need Girovo Plus you get not_permitted.

girovo.reminders.setPermission: budgets.write
{ contract, by | null, lead?: 7 | 14 | 30 }

{ contract, reminder }. A reminder for a contract, lead days before.

girovo.contracts.renamePermission: budgets.write
{ contract, name | null }

{ contract, name, own }. null brings back the detected name.

Settings

girovo.settings.getPermission: settings.read
{}

{ settings, onlyOn[], onlyOff[] }: the settings your app can read and change. onlyOn and onlyOff name the ones that only go one way.

girovo.settings.setPermission: settings.write
{ key, value }

{ key, value, changed }. Other keys give invalid.

Allowed keys: language, theme, blackBackground, idleMinutes (5, 10, 15, 30), balancesAtLogin, bookingsAtLogin, keepBalances and keepBookings (on only), companyLogos (off only), ai, aiTone, aiLength, aiUseName, aiNames, singleKeyShortcuts, accountOrder, accountGrouping (type, bank, none), hiddenAccounts.

Bank and transfers

girovo.bank.refreshPermission: bank.refresh
{ account? }

{ accounts: [{ account, state, newBookings?, balance? }], newBookings }. Fetches new data as Girovo itself does. The user gives any approval in Girovo. Can take up to 10 minutes.

girovo.transfers.requestPermission: transfers.request
{ iban, name, cents, purpose?, executeOn?, account? }

{ request, state: "pending" }. Girovo opens the transfer filled in, marked as asked by your app. With executeOn it becomes a scheduled transfer where the account offers them.

girovo.transfers.statusPermission: transfers.request
{ request }

{ request, state, text?, changed? }. Your own requests only.

state is pending until the user acts. Then sent (the bank took it), refused (closed without sending), failed (the bank refused it, its words in text) or unclear (not yet known whether it went through). changed: true means the user sent other values. Girovo does not say which. The result also comes as the event girovo.transfer.updated on the connection that asked.

Events

girovo.events.subscribePermission: per kind
{ kinds[] }

{ subscribed[] }

girovo.events.unsubscribePermission: none
{ kinds[]? }

{ subscribed[] }. Without kinds every subscription of this connection ends.

Errors

An error is a JSON-RPC error. error.data.reason is the code from this list, and error.data.detail the finer reason where there is one, such as never for a setting no app may change. Fields such as permission, param or text come along. The message is meant for your log, not for your interface.

ResponseJSON
{
  "jsonrpc": "2.0",
  "id": 2,
  "error": {
    "code": -32003,
    "message": "This app does not have the permission for this method.",
    "data": {
      "reason": "not_permitted",
      "permission": "insights.read"
    }
  }
}
reasoncodeMeaning
locked-32001Girovo is locked or nobody is signed in. Try again later.
denied-32002The user refused your app. Until Girovo restarts this holds for everything.
not_permitted-32003Your app lacks a permission, data.permission names it. Without data.permission, message gives the reason, such as too many open transfer requests.
refused_by_user-32004The user refused in the dialog or at the approval.
bank_error-32005The bank reported an error, its words in data.text.
timeout-32006No answer in time, for example to the dialog. You may ask again.
rate_limited-32007Too many calls in a short time.
too_large-32008The message is larger than 1 MB.
busy-32009Too many connections or waiting calls.
invalid-32602A parameter is wrong, data.param names it. Also any call before hello.
method_not_found-32601This method does not exist.
internal-32603Girovo could not answer the call.

Events

With girovo.events.subscribe your connection listens for events. They come as JSON-RPC notifications without an id, with the method girovo.<kind>. Each kind needs a permission. If the user takes it back, its events stop at once. A subscription holds only for this connection.

There and backJSON
→ {"jsonrpc":"2.0","id":3,"method":"girovo.events.subscribe","params":{"kinds":["bookings.new","balances.changed"]}}
← {"jsonrpc":"2.0","id":3,"result":{"subscribed":["bookings.new","balances.changed"]}}
← {"jsonrpc":"2.0","method":"girovo.balances.changed","params":{"account":"a_…","balance":{…},"available":{…}}}
bookings.newPermission: transactions.read
{ account, count, bookings[] }

New bookings on an account that was already loaded.

balances.changedPermission: accounts.read
{ account, balance, available }

A balance changed.

transfer.updatedPermission: transfers.request
{ request, state, text?, changed? }

A transfer request from your app is settled.

Example in Node.js

Connect, hello, read the accounts. No package needed, Node 18 or newer will do. Girovo must be running with Girovo API switched on.

girovo-client.mjsNode.js
// girovo-client.mjs: node girovo-client.mjs
import net from 'node:net'
import { execFileSync } from 'node:child_process'

function address() {
  if (process.platform === 'win32') {
    // "PC\\name","S-1-5-21-…"
    const line = execFileSync('whoami', ['/user', '/fo', 'csv', '/nh'], { encoding: 'utf8' })
    const sid = line.trim().split(',').pop().replaceAll('"', '')
    return '\\\\.\\pipe\\girovo-api-' + sid
  }
  return process.env.XDG_RUNTIME_DIR + '/girovo-api.sock'
}

const socket = net.connect(address())
socket.setEncoding('utf8')
const waiting = new Map()
let nextId = 1
let buffer = ''

function call(method, params = {}) {
  const id = nextId++
  socket.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n')
  return new Promise((resolve, reject) => waiting.set(id, { resolve, reject }))
}

socket.on('data', (chunk) => {
  buffer += chunk
  let at
  while ((at = buffer.indexOf('\n')) >= 0) {
    const line = buffer.slice(0, at)
    buffer = buffer.slice(at + 1)
    if (!line.trim()) continue
    const message = JSON.parse(line)
    if (!('id' in message)) continue // an event
    const { resolve, reject } = waiting.get(message.id)
    waiting.delete(message.id)
    if (message.error) reject(message.error)
    else resolve(message.result)
  }
})

socket.on('connect', async () => {
  try {
    const hello = await call('girovo.hello', {
      app: 'My Household Book',
      version: '1.0',
      permissions: ['accounts.read'],
      reason: 'Shows your balances in the household book.',
    })
    console.log('Allowed:', hello.granted)
    const { accounts } = await call('girovo.accounts.list')
    for (const a of accounts) console.log(a.name, a.balance?.cents, a.balance?.currency)
  } catch (error) {
    console.error(error.data?.reason, error.message)
  } finally {
    socket.end()
  }
})

Girovo MCP

Girovo MCP connects AI assistants such as Claude Desktop or Codex to Girovo. It is an MCP server over stdio that uses Girovo API. The assistant gets the same dialogs and permissions as any other app.

Where it is

Windows%LOCALAPPDATA%\Programs\Girovo\girovo-mcp.exe
Linux (deb)/usr/bin/girovo-mcp
Linux (AppImage)~/.config/Girovo/girovo-mcp

Claude Desktop

Add Girovo MCP to claude_desktop_config.json. On Windows the file is in %APPDATA%\Claude. Write out the full path and double every backslash in JSON.

claude_desktop_config.jsonJSON
{
  "mcpServers": {
    "girovo": {
      "command": "C:\\Users\\DEIN-NAME\\AppData\\Local\\Programs\\Girovo\\girovo-mcp.exe"
    }
  }
}

On Linux, command is the full path, for example "/usr/bin/girovo-mcp". Restart the assistant afterwards.

Other MCP clients

The same goes for Codex and other clients: the command is the path to girovo-mcp, with no arguments.

How it behaves

  • With the first tool, Girovo MCP asks for accounts.read, transactions.read and insights.read, plus that tool's permission. So one dialog covers the start.
  • If a later tool needs another permission, Girovo opens the dialog just for that one.
  • On Windows the dialog shows the assistant, for example "Claude (Anthropic PBC) über Girovo MCP". On Linux it shows Girovo MCP with its path.
  • If Girovo is closed or Girovo API is off, the tool says so. The next call connects again.

The 19 tools

ToolMethodPermission
list_accountsaccounts.listaccounts.read
search_transactionstransactions.listtransactions.read
month_summaryinsights.monthinsights.read
list_contractscontracts.listinsights.read
list_budgetsbudgets.listinsights.read
list_savings_goalsgoals.listinsights.read
set_booking_categorycategories.setcategories.write
add_category_rulerules.addcategories.write
create_categorycategories.createcategories.write
set_notenotes.setnotes.write
set_budgetbudgets.setbudgets.write
set_savings_goalgoals.setbudgets.write
set_reminderreminders.setbudgets.write
rename_contractcontracts.renamebudgets.write
get_settingssettings.getsettings.read
set_settingsettings.setsettings.write
refresh_from_bankbank.refreshbank.refresh
request_transfertransfers.requesttransfers.request
transfer_statustransfers.statustransfers.request

If your assistant runs in the cloud, everything it reads from Girovo goes to its provider.

Limits

What Girovo allows

Connections per program4 (16 in all)
First call after connectingwithin 30 s
Calls per second20, briefly up to 40
Open calls per connection32
Size of a message1 MB
Answer to a callup to 150 s
girovo.bank.refreshup to 10 minutes
Waiting for the login2 minutes, 64 calls at most
Open transfer requests3 per app, 10 in all

What no app can do

  • The PIN, Windows Hello and security keys
  • "Weitere Banken mit anmelden" and forgetting the device
  • Deleting or resetting saved data, restoring a backup
  • Girovo API itself: the switch and the permissions, its own included
  • The update channel and updates
  • Usage data, "Neue Umsätze melden" and the sync
  • Sending a transfer: the user always does that in Girovo

Girovo is source available under the PolyForm Perimeter License 1.0.1. You can find the source code on GitHub.