{"ok":true,"data":{"service":{"name":"Expense Budget Tracker Agent API","version":"v1","description":"Machine API for onboarding, workspace setup, and restricted SQL."},"auth":{"bootstrapUrl":"https://auth.expense-budget-tracker.com/api/agent/send-code","scheme":"Authorization: ApiKey <key>","oauth":{"issuer":"https://auth.expense-budget-tracker.com","scopes":["expenses:read","expenses:write"]}},"apiBaseUrl":"https://api.expense-budget-tracker.com/v1","authBaseUrl":"https://auth.expense-budget-tracker.com","mcp":{"url":"https://mcp.expense-budget-tracker.com/mcp","transport":"streamable-http"},"docs":{"discoveryUrl":"https://api.expense-budget-tracker.com/v1/","docsUrl":"https://github.com/kirill-markin/expense-budget-tracker/blob/main/README.md","source":{"repositoryUrl":"https://github.com/kirill-markin/expense-budget-tracker","sqlApiUrl":"https://github.com/kirill-markin/expense-budget-tracker/tree/main/apps/sql-api/src","authRoutesUrl":"https://github.com/kirill-markin/expense-budget-tracker/tree/main/apps/auth/src/routes"}},"capabilities":["Load account context","Select a workspace","Inspect allowed SQL schema and hints","Run restricted SQL scripts"]},"actions":[{"name":"send_code","method":"POST","description":"Start email OTP. After this succeeds, tell the user to check spam or junk if the email is not visible, then ask for the 8-digit code and call verify_code. Do not suggest immediately requesting another code.","url":"https://auth.expense-budget-tracker.com/api/agent/send-code","input":{"email":"string"},"auth":"none"},{"name":"schema","method":"GET","description":"Inspect allowed relations, columns, and hints. Relation operations: ledger_entries, budget_lines, budget_adjustments, workspace_settings, and account_metadata support SELECT and, under existing write-approval rules, INSERT, UPDATE, and DELETE; the derived accounts view and the global worker-owned fx_rates_raw and fx_rates_daily relations are SELECT-only.","url":"https://api.expense-budget-tracker.com/v1/schema","auth":"ApiKey"},{"name":"run_sql_query","method":"POST","description":"Run exactly one read-only SELECT or WITH...SELECT statement.","url":"https://api.expense-budget-tracker.com/v1/sql/query","input":{"sql":"string","X-Workspace-Id":"optional string"},"auth":"ApiKey"},{"name":"run_sql_execute","method":"POST","description":"Run exactly one explicitly approved INSERT, UPDATE, or DELETE mutation.","url":"https://api.expense-budget-tracker.com/v1/sql/execute","input":{"sql":"string","X-Workspace-Id":"optional string"},"auth":"ApiKey"}],"instructions":"## Onboarding and endpoints\n\nAsk the user for their email address first, then call send_code. The same email OTP flow handles both signup and login. After send_code succeeds, tell the user to check spam or junk if the email is not visible, then ask for the 8-digit code and call verify_code. Do not suggest immediately requesting another code.\nAfter login, save the returned key outside chat memory, preferably in a local .env file as EXPENSE_BUDGET_TRACKER_API_KEY='<PASTE_KEY_HERE>', then call https://api.expense-budget-tracker.com/v1/me, https://api.expense-budget-tracker.com/v1/workspaces, and https://api.expense-budget-tracker.com/v1/workspaces/{workspaceId}/select before SQL.\nUse https://api.expense-budget-tracker.com/v1/schema to inspect allowed relations, columns, and any agent hints about constraints or write semantics.\nSend one read-only SELECT or WITH...SELECT statement to https://api.expense-budget-tracker.com/v1/sql/query. Send one explicitly approved INSERT, UPDATE, or DELETE statement to https://api.expense-budget-tracker.com/v1/sql/execute. Legacy https://api.expense-budget-tracker.com/v1/sql remains available only for compatibility and atomic multi-statement scripts.\nExample: curl -H 'Authorization: ApiKey $EXPENSE_BUDGET_TRACKER_API_KEY' https://api.expense-budget-tracker.com/v1/me.\n\n## Restricted SQL dialect\n\nRestricted SQL accepts SELECT, WITH, INSERT, UPDATE, and DELETE. Send one statement per call unless the entrypoint you use documents semicolon-separated scripts.\nRelation operations: ledger_entries, budget_lines, budget_adjustments, workspace_settings, and account_metadata support SELECT and, under existing write-approval rules, INSERT, UPDATE, and DELETE; the derived accounts view and the global worker-owned fx_rates_raw and fx_rates_daily relations are SELECT-only. Only allowlisted relations are reachable; internal and security-related relations are blocked.\nOnly these function calls are supported: SUM, COUNT, MIN, MAX, AVG, ARRAY_AGG, BOOL_AND, BOOL_OR, STDDEV, STDDEV_SAMP, STDDEV_POP, VARIANCE, VAR_SAMP, VAR_POP, COALESCE, DATE_TRUNC, DATE_PART, EXTRACT, ABS, ROUND, CEIL, FLOOR, TRUNC, MOD, POWER, SQRT, NOW, CURRENT_TIMESTAMP, CAST, NULLIF, GREATEST, LEAST, LOWER, UPPER, INITCAP, LENGTH, TO_CHAR, TRIM, BTRIM, SUBSTRING, LEFT, RIGHT, POSITION, STRPOS, STARTS_WITH, REPLACE, REGEXP_REPLACE, SPLIT_PART, CONCAT, STRING_AGG, ROW_NUMBER, LAG, LEAD, RANK, DENSE_RANK, FIRST_VALUE, LAST_VALUE. Every other function is blocked, including gen_random_uuid, set_config, and workspace or auth helper functions.\nWindow functions over the allowlisted names work with OVER (PARTITION BY ... ORDER BY ...) and an optional frame, aggregates accept FILTER (WHERE ...), and the keyword call forms EXTRACT(field FROM value), SUBSTRING(value FROM start FOR count), TRIM(BOTH chars FROM value), and POSITION(needle IN haystack) are supported.\nDISTINCT ON, named WINDOW clauses, GROUP BY ROLLUP, CUBE and GROUPING SETS, WITHIN GROUP ordered-set aggregates, and parenthesized table lists such as FROM a, (b) are not supported: rank rows with ROW_NUMBER() OVER (...) and keep rn = 1 instead of DISTINCT ON, repeat a named window inline in every OVER (...), run one statement per grouping level, compute ordered-set aggregates outside SQL, and reference each relation directly in the source list or use a parenthesized subquery such as FROM (SELECT ...) alias instead of a parenthesized table list.\nPrefer ILIKE over LOWER(...) for case-insensitive text matching.\nPrefer explicit date literals calculated before running SQL, and filter with closed-open ranges: ts >= start date and ts < exclusive end date. Never write NOW() into a stored value such as ledger_entries.ts; calculate an explicit literal for every value you insert or update.\nUse regular single-quoted literals and double an embedded apostrophe, for example 'customer''s'. Dollar-quoted strings and E'...' escape strings are not supported.\nON CONFLICT is not supported. Read first, then run an explicit INSERT when the row is missing or an explicit UPDATE when the row already exists.\nINSERT statements must set workspace_id explicitly; read it from workspace_settings first.\nA SELECT returns at most 100 rows per statement, some entrypoints additionally apply one shared 100-row returned-row budget across all statements of a single call that SELECT rows and mutation RETURNING rows both consume, and a mutation may affect at most 100 rows per call, so split larger changes into sequential calls.\nEvery result is JSON with an ok flag; when ok is false, read the error message and fix the statement before retrying. Before treating a result set as complete, compare returnedRowCount with totalRowCount and check truncated, and narrow the query when the result was capped.\n\n## Writing data\n\nBefore any write (INSERT, UPDATE, DELETE), describe the exact changes you plan to make and wait for the user's explicit approval. Reads (SELECT) never need approval.\nTreat this protocol as session-scoped, not message-scoped. If you already completed a step earlier in the same session and nothing relevant changed, reuse those results instead of repeating the same calls. Repeat a step only when the user provided new data that affects it, a previous result was interrupted or marked unknown, or the database may have changed after a write.\n\n### Approval and execution\n\nShow the complete plan before asking for approval: every entry including both sides of each transfer pair, and the balance math for each affected account as current balance plus the sum of new entries equals the expected balance. That balance math is an internal check; ask the user to confirm it against their own view only when it reveals a real mismatch or an unresolved ambiguity.\nOne explicit approval covers the full approved change set, including the probe and every remaining batch. After approval, execute the probe automatically instead of treating it as a second checkpoint.\nStart with a tiny probe in the same SQL shape: 1-3 literal rows for INSERT, 1 targeted row for UPDATE or DELETE. If the probe fails, stop, show the exact error, fix the SQL, and retry the small version. If the probe succeeds, immediately continue with the remaining approved data in sequential batches of at most 100 rows per call, and prefer several sequential calls over one oversized batch.\nDo: probe succeeds -> continue with the next batch immediately.\nDon't: probe succeeds -> ask \"A or B\" or request renewed approval unless execution failed or a new ambiguity appeared.\nWhen the user delegates reasonable assumptions, says to use best judgment or best guess, or says decide for me, proceed, or continue, treat unresolved account naming, category naming, and heuristic mapping choices as approved defaults for that import. State the assumptions briefly and keep executing.\nDo not write optional sidecar data on your own initiative. Write it only when the user explicitly asks to set or override it.\n\nThe Writing data sections above are an excerpt of the shared write guide; the full guide additionally covers discovery before writing, entry shapes, source rows and dates, the per-entry checklist, budget rows, batching questions, progress and resuming, and final verification, and is not available over this API."}