Install
openclaw skills install @winxton/openfinanceopenclaw skills install @winxton/openfinanceQuery your financial accounts and transactions via the OpenFinance API.
export OPENFINANCE_API_KEY="your_api_key_here"
All commands below use these variables:
BASE_URL="${OPENFINANCE_URL:-https://api.openfinance.sh}"
AUTH_HEADER="Authorization: Bearer $OPENFINANCE_API_KEY"
Fetch all connected financial accounts with balances and institution info.
curl -s "$BASE_URL/api/accounts" -H "$AUTH_HEADER" | cat
Response shape per account:
id (number) — account ID (use for filtering transactions)name, officialName — account namestype (e.g. "depository", "credit"), subtype (e.g. "checking", "credit card")mask — last 4 digitscurrentBalance, availableBalance, isoCurrencyCodeinstitutionName, institutionUrlstatus — "active" or "hidden"isSyncing (boolean), syncError (object or null)A non-null syncError means the bank connection needs the owner to sign in again. Use Create Reconnect Link to fix it.
Search and filter transactions. Returns newest first by default.
curl -s -G "$BASE_URL/api/transactions" \
-H "$AUTH_HEADER" \
--data-urlencode "startDate=YYYY-MM-DD" \
--data-urlencode "endDate=YYYY-MM-DD" \
--data-urlencode "search=coffee" \
--data-urlencode "merchants=Starbucks,Walmart" \
--data-urlencode "accountId=123" \
--data-urlencode "limit=100" \
--data-urlencode "cursor=YYYY-MM-DD:ID" \
--data-urlencode "pending=false" \
--data-urlencode "status=active,hidden" \
--data-urlencode "fields=name,amount,date,merchantName" \
--data-urlencode 'amountFilters=[{"operator":">","amount":100}]' \
| cat
All query parameters are optional. Only include the ones you need.
| Parameter | Type | Description |
|---|---|---|
startDate | YYYY-MM-DD | Start date filter |
endDate | YYYY-MM-DD | End date filter |
search | string | Search by transaction name or merchant |
merchants | comma-separated | Filter by exact merchant names |
accountId | number | Filter by account ID |
limit | number | Max results (default 100, max 500) |
cursor | string | Next page: <date>:<id> of the last transaction you received. Date sort only |
pending | boolean | Filter pending transactions |
status | comma-separated | Filter by status: active, hidden, deleted |
fields | comma-separated | Return only these fields per transaction (reduces payload) |
amountFilters | JSON array | Filter by amount, e.g. [{"operator":">","amount":50}] |
id, name, amount, date, authorizedDate, pending, merchantName, isoCurrencyCode, accountId, status, createdAt, updatedAt
Run one validated SQL SELECT (maximum 2000 characters) against the unqualified txns relation for aggregations, grouping, and analysis. Only the caller's transactions are available; deleted transactions and hidden accounts are excluded. The query runs read-only with a 5-second timeout and a maximum of 1000 result rows. Aggregations still use all matching input rows, and smaller LIMITs and OFFSETs are preserved.
Filtering, DISTINCT, grouping, HAVING, sorting, subqueries, joins, and UNION/UNION ALL are supported. Every table source must be txns or a derived SELECT whose sources are txns. Other tables and schemas, caller-defined CTEs, window functions, SELECT INTO, locking clauses, table functions, transaction commands, and multiple statements are rejected.
Approved functions: COUNT, SUM, AVG, MIN, MAX, ABS, ROUND, CEIL, CEILING, FLOOR, LOWER, UPPER, LENGTH, TRIM, BTRIM, LTRIM, RTRIM, SUBSTRING, SUBSTR, REPLACE, DATE_TRUNC, DATE_PART, EXTRACT, TO_CHAR. CASE, COALESCE, NULLIF, GREATEST, LEAST, CURRENT_DATE, and CURRENT_TIMESTAMP are also supported. Casts are limited to built-in boolean, integer, numeric, floating-point, text, date, timestamp, and interval types. Arithmetic, comparisons, LIKE/ILIKE, IN, BETWEEN, and NULL/boolean tests are supported; custom operators, regex operators, COLLATE, and unlisted syntax are rejected. Function and type names may be unqualified or qualified with pg_catalog.
curl -s -X POST "$BASE_URL/api/transactions/query" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT SUM(amount), COUNT(*) FROM txns WHERE merchant_name ILIKE '\''%starbucks%'\''"}' \
| cat
txns CTE columnsid, name, amount (numeric), date, authorized_date, merchant_name, pending, iso_currency_code, account_id, status, created_at, updated_at
Note: SQL column names use snake_case (e.g. merchant_name), while the REST API returns camelCase (e.g. merchantName).
Monthly spend breakdown:
SELECT TO_CHAR(date, 'YYYY-MM') as month, SUM(amount) as total, COUNT(*) as count
FROM txns GROUP BY 1 ORDER BY 1
Top merchants by total spend:
SELECT COALESCE(merchant_name, name) as merchant, SUM(amount) as total
FROM txns GROUP BY 1 ORDER BY total DESC LIMIT 10
Spending in a date range:
SELECT SUM(amount) as total FROM txns
WHERE date >= '2026-01-01' AND date < '2026-02-01'
Large transactions:
SELECT name, merchant_name, amount, date FROM txns
WHERE amount > 500 ORDER BY amount DESC
Success: { "rows": [...], "rowCount": N }
Error: { "error": "error message" } — fix the SQL and retry.
Create a public link that reconnects a disconnected account without signing in to OpenFinance. Give the URL to the account's owner. Opening it walks them through their bank's sign-in (Plaid or Quiltt), then shows the account re-syncing. Only the owner can finish this step: bank credentials and verification codes can't be sent through the API.
Find accounts that need reconnecting:
curl -s "$BASE_URL/api/accounts" -H "$AUTH_HEADER" \
| jq '.accounts[] | select(.syncError != null) | {id, institutionName, syncError}'
Create a link for one of them:
curl -s -X POST "$BASE_URL/api/accounts/ACCOUNT_ID/reconnect-link" \
-H "$AUTH_HEADER" \
| cat
Response (201):
{
"url": "https://app.openfinance.sh/reconnect/<token>",
"expiresAt": "2026-01-02T15:04:05.000Z",
"accountId": 123,
"institutionName": "Chase",
"provider": "plaid"
}
GET /api/accounts: the account's syncError turns null as soon as the owner reconnects, and isSyncing stays true until the re-sync finishes, which can take a few minutes.404: no account with that ID. 422: the account's provider (MX) isn't supported.COALESCE(merchant_name, name).fields parameter on GET /api/transactions to reduce payload size when you only need a few columns.limit transactions, request the next one with cursor=<date>:<id> from the last transaction, keeping the other parameters the same. Cursors only work with the default date sort.txns CTE directly — do not define your own CTE or use transaction control statements.syncError, create a reconnect link and send it to the owner rather than asking them to open the app.