A safety layer proxy for Estonian e-Financials (e-arveldaja) API that allows AI agents to freely read bookkeeping data while capturing all write operations for human approval.
Key Principle: AI agents can READ and PROPOSE changes, but cannot EXECUTE changes without human approval.
- Read-Only by Default: AI agents can freely query all data (accounts, journals, transactions, invoices)
- Write Protection: All POST/PATCH/PUT/DELETE operations are captured and queued for approval
- Beautiful UI: Web interface with specialized visualization for double-entry bookkeeping
- MCP Server: AI assistants can interact through Model Context Protocol
- Audit Trail: Complete history of all proposed and executed changes
- Secure: HMAC-SHA-384 authentication compatible with e-Financials API
Overview of company info, account balances, and all pending changes with their status.
Detailed view of journal entries awaiting approval, showing account codes, names, and debit/credit amounts.
npm installCopy the example file and fill in your credentials:
cp .env.example .envAPI_KEY_ID=your_api_key_id
API_KEY_PUBLIC=your_api_key_public
API_KEY_PASSWORD=your_api_key_password
API_BASE_URL=https://demo-rmp-api.rik.ee/v1
PORT=3000
# Optional hardening (see Security section)
# ALLOWED_ORIGINS=http://localhost:3000
# PROXY_AUTH_TOKEN=change-me| Variable | Required | Description |
|---|---|---|
API_KEY_ID |
Yes | e-Financials API key ID (used in the signed payload) |
API_KEY_PUBLIC |
Yes | e-Financials API public key (sent in X-AUTH-KEY) |
API_KEY_PASSWORD |
Yes | e-Financials API password (HMAC-SHA-384 signing key) |
API_BASE_URL |
No | Defaults to the demo environment https://demo-rmp-api.rik.ee/v1 |
PORT |
No | Server port, defaults to 3000 |
DB_PATH |
No | SQLite database path; defaults to pending_changes.db in the project root (shared by the proxy and MCP servers) |
ALLOWED_ORIGINS |
No | Comma-separated CORS allowlist; defaults to http://localhost:<PORT> |
MCP_ALLOWED_HOSTS |
No | Comma-separated Host headers accepted on /mcp (DNS rebinding protection); defaults to localhost and 127.0.0.1 with and without the port |
PROXY_AUTH_TOKEN |
No | When set, all write operations (proxy writes, approvals, rejections, deletes) and all MCP-over-HTTP requests require this token via Authorization: Bearer <token> or X-Proxy-Token header. The review UI picks it up from ?token=<token> in the URL. |
npm run devThe server will start on port 3000 (or PORT env var).
Access points:
- Web UI: http://localhost:3000/review
- MCP endpoint: http://localhost:3000/mcp
- Health check: http://localhost:3000/health
- API connectivity test: http://localhost:3000/test-connection
- USAGE.md - Complete usage guide for humans (installation, configuration, common tasks, troubleshooting)
- AGENTS.md - Instructions for AI assistants working with this project
- API Documentation - API reference below
AI Agent/Client → Proxy → [If GET] → e-Financials API
→ [If WRITE] → SQLite Queue → Web UI → User Approval
↓
[If Approved] → e-Financials API
- Read Operations (
GETrequests): Pass through directly to the e-Financials API - Write Operations (
POST/PUT/PATCH/DELETE): Captured in SQLite, queued for approval - Human Review: Use the web UI at
/reviewto approve or reject changes - Execution: Only approved changes are sent to the actual e-Financials API
Open http://localhost:3000/review to:
- See all pending changes with journal entry visualization
- Approve or reject individual changes
- Approve or reject entire changesets
- Delete individual changesets or bulk-delete filtered changesets
- View executed changes and their results
AI assistants use the MCP server to interact safely:
Reading data:
query_api with endpoint: "/accounts"
Proposing changes:
propose_change:
endpoint: "/journals"
method: "POST"
body: {
"no": "J-2024-001",
"effective_date": "2024-01-15",
"description": "Office supplies",
"transactions": [
{"debit_account": "5140", "credit_account": "1020", "amount": "125.50"}
]
}
description: "Record office supplies purchase"
Checking status:
list_pending_changes
See AGENTS.md for complete AI assistant guidelines.
The running proxy server exposes the MCP server at http://localhost:3000/mcp (Streamable HTTP) — no separate process needed. Add to your MCP client config (e.g. ~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"arveldaja": {
"type": "http",
"url": "http://localhost:3000/mcp"
}
}
}If PROXY_AUTH_TOKEN is set, every MCP request needs it (MCP messages are all POSTs, so the write guard applies):
{
"mcpServers": {
"arveldaja": {
"type": "http",
"url": "http://localhost:3000/mcp",
"headers": { "Authorization": "Bearer <your PROXY_AUTH_TOKEN>" }
}
}
}The proxy token only gates access to the proxy itself — it is not an e-Financials API credential, so it is acceptable in the client config. Agents can only read data and propose changes with it; execution still requires human approval in the review UI.
For MCP clients that only support stdio servers, the standalone entry point still exists:
{
"mcpServers": {
"arveldaja": {
"command": "node",
"args": ["/full/path/to/arveldaja-proxy/dist/mcp-server.js"]
}
}
}In both setups the server reads the API credentials from the .env file in the project root, so the MCP client config carries no secrets — agent hosts can read their own configuration, and an agent must never be able to see the credentials. Do not add the keys to an env block. (Real environment variables still take precedence over .env if you set them on the process some other way.)
Reading:
query_api- Query any e-Financials endpoint
Proposing:
propose_change- Propose changes (captured, not executed); passchangesetIdto group related changescreate_changeset- Create a changeset to group related proposals for a single review
Checking:
list_pending_changes- See proposed changes; filter bystatusto see execution results or rejection reasonslist_changesets- View all changesetsget_changeset_details- Review a changeset including each change's outcome
GET /proxy/v1/*- Forward read requests to e-FinancialsPOST/PATCH/PUT/DELETE /proxy/v1/*- Capture write requests
Captured writes get an auto-created single-change changeset. To group several writes together, first create a changeset via POST /api/changesets and pass its ID in an X-Changeset-Id header on each write.
Example:
# Read accounts (passes through)
curl http://localhost:3000/proxy/v1/accounts
# Create journal (captured)
curl -X POST http://localhost:3000/proxy/v1/journals \
-H "Content-Type: application/json" \
-d '{
"no": "J-001",
"effective_date": "2024-01-15",
"description": "Test entry",
"transactions": [
{"debit_account": "5140", "credit_account": "1020", "amount": "100.00"}
]
}'GET /api/changes- List all changes (optional?status=pending|approved|rejected)GET /api/changes/:id- Get specific changePOST /api/changes/:id/approve- Approve and executePOST /api/changes/:id/reject- Reject changeDELETE /api/changes/:id- Delete a changeGET /api/changesets- List changesets (optional?status=pending|approved|rejected)GET /api/changesets/:id- Get changeset with changesPOST /api/changesets- Create a changeset (body:{name, description?})POST /api/changesets/:id/approve- Approve and execute all in changesetPOST /api/changesets/:id/reject- Reject all in changesetPOST /api/changesets/:id/changes- Move changes into a changeset (body:{changeIds: [...]})DELETE /api/changesets/:id- Delete one changeset and its captured changesDELETE /api/changesets- Delete all changesets (optional?status=pending|approved|rejected)GET /api/stats- Get statistics
Aggregated views computed by the proxy from e-Financials data (used by the web UI):
GET /api/company- Company info (VAT registration, invoice settings, bank accounts)GET /api/accounts- Chart of accountsGET /api/account-balances?startDate=YYYY-MM-DD&endDate=YYYY-MM-DD- Opening/closing balances and period changes per account (optional&accounts=1020,5140and&includeDimensions=true)GET /api/account-dimensions?account=1750- Dimension usage statistics for an account
- For
/journalswrites, the proxy converts simplifiedtransactionsto APIpostings. - If a journal payload contains
descriptionbut notitle, the proxy automatically setstitle = descriptionso the journal description is visible in e-Financials.
POST /mcp- MCP server over Streamable HTTP (stateless; see MCP Server Setup)
GET /reviewandGET /review/:id- Review interfaceGET /health- Health checkGET /test-connection- Verify e-Financials API credentials and connectivity
npm run build # Compile TypeScript
npm run dev # Run with hot reload
npm start # Run production build (includes the /mcp endpoint)
npm run mcp # Run standalone stdio MCP server
npm run lint # Run ESLint
npm run typecheck # Run TypeScript checkpublic/
└── index.html # Review web UI (single page)
src/
├── index.ts # Main Express server (CORS, auth guard, proxy routes)
├── mcp-server.ts # Standalone stdio entry for the MCP server
├── db/
│ └── index.ts # SQLite database operations
├── mcp/
│ └── server.ts # MCP server factory (tools and handlers)
├── middleware/
│ └── capture.ts # Request interception middleware
├── routes/
│ ├── api.ts # Change management API
│ ├── changesets.ts # Changeset management API
│ ├── company.ts # Company info and balance aggregation
│ └── mcp.ts # MCP over Streamable HTTP (/mcp)
├── types/
│ └── index.ts # Shared TypeScript types
└── utils/
├── auth.ts # HMAC-SHA-384 signing
├── executor.ts # Forward reads, execute approved changes
└── locks.ts # In-process locks against double approval
- API Credentials: Loaded from the project root
.envby the node processes themselves — never placed in MCP client configs or other agent-readable locations - Approval Required: All writes require explicit human approval
- Audit Trail: Complete history stored in SQLite
- No Secrets Logged: Credentials never appear in logs
- CORS Allowlist: Browser origins are restricted to
ALLOWED_ORIGINS(default: the local review UI) so arbitrary websites cannot issue write/approval requests - Optional Write Token: Set
PROXY_AUTH_TOKENto require a shared secret on all mutating requests, including approvals - Host Pinning: Forwarded requests are rejected unless they resolve to the configured API origin, so signed auth headers can't leak to other hosts
- Double-Approval Protection: In-process locks prevent concurrent approvals from executing the same change twice
MIT License - see LICENSE file for details.
- Documentation: See USAGE.md and AGENTS.md
- Issues: https://github.com/v3rm0n/arveldaja-proxy/issues

