{"openapi":"3.1.0","info":{"title":"Expense Budget Tracker API","version":"v1","description":"Machine-facing API for expense-budget-tracker."},"servers":[{"url":"https://api.expense-budget-tracker.com/v1","description":"Canonical API host"},{"url":"https://auth.expense-budget-tracker.com","description":"Auth bootstrap host"}],"paths":{"/":{"get":{"summary":"API discovery index","description":"Start here for agent onboarding. Ask the user for their email address first, call send_code, and use the same email OTP flow for 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. Use /schema before mutating SQL to inspect allowed relations, constraints, and write semantics. Relation operations: ledger_entries, budget_lines, workspace_settings, and account_metadata support SELECT and, under existing write-approval rules, INSERT, UPDATE, and DELETE; the derived accounts view and global worker-owned fx_rates_raw and fx_rates_daily relations are SELECT-only. Restricted SQL does not support ON CONFLICT. Only SUM, COUNT, MIN, MAX, AVG, and COALESCE function calls are allowed; all other functions are blocked. Use ILIKE instead of LOWER(...) for case-insensitive text search and explicit date ranges instead of NOW() or DATE_TRUNC(). Use regular single-quoted literals. Dollar-quoted strings are not allowed.","responses":{"200":{"description":"Discovery envelope"}}}},"/agent":{"get":{"summary":"Agent discovery","description":"Agent onboarding entrypoint. Ask the user for their email address first, call send_code, and use the same email OTP flow for 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. Use /schema before mutating SQL to inspect allowed relations, constraints, and write semantics. Relation operations: ledger_entries, budget_lines, workspace_settings, and account_metadata support SELECT and, under existing write-approval rules, INSERT, UPDATE, and DELETE; the derived accounts view and global worker-owned fx_rates_raw and fx_rates_daily relations are SELECT-only. Restricted SQL does not support ON CONFLICT. Only SUM, COUNT, MIN, MAX, AVG, and COALESCE function calls are allowed; all other functions are blocked. Use ILIKE instead of LOWER(...) for case-insensitive text search and explicit date ranges instead of NOW() or DATE_TRUNC(). Use regular single-quoted literals. Dollar-quoted strings are not allowed.","responses":{"200":{"description":"Discovery envelope"}}}},"/openapi.json":{"get":{"summary":"OpenAPI document","responses":{"200":{"description":"OpenAPI JSON document"}}}},"/swagger.json":{"get":{"summary":"OpenAPI document alias","responses":{"200":{"description":"OpenAPI JSON document"}}}},"/me":{"get":{"summary":"Load account context","description":"Use this after login. The response instructions and actions guide the next calls: /workspaces, then /workspaces/{workspaceId}/select.","security":[{"ApiKeyHeader":[]}],"responses":{"200":{"description":"Account envelope"}}}},"/workspaces":{"get":{"summary":"List workspaces","description":"Call this after /me to obtain workspaceId values. Then call /workspaces/{workspaceId}/select to save the workspace for subsequent SQL.","security":[{"ApiKeyHeader":[]}],"responses":{"200":{"description":"Workspace list envelope"}}},"post":{"summary":"Create workspace","security":[{"ApiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":100}}}}}},"responses":{"200":{"description":"Workspace created envelope"}}}},"/workspaces/{workspaceId}/select":{"post":{"summary":"Validate and save workspace for later SQL","description":"Validates membership and saves the workspace for the current API key connection. After this, /sql can omit X-Workspace-Id unless you want to override the saved workspace.","security":[{"ApiKeyHeader":[]}],"parameters":[{"in":"path","name":"workspaceId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Workspace ready envelope"}}}},"/schema":{"get":{"summary":"List allowed relations, columns, and optional hints","description":"Returns only relations supported by /sql. The response includes relation columns and may include optional agent hints about risky constraints or write semantics. Relation operations: ledger_entries, budget_lines, workspace_settings, and account_metadata support SELECT and, under existing write-approval rules, INSERT, UPDATE, and DELETE; the derived accounts view and global worker-owned fx_rates_raw and fx_rates_daily relations are SELECT-only.","security":[{"ApiKeyHeader":[]}],"responses":{"200":{"description":"Schema envelope"}}}},"/sql":{"post":{"summary":"Execute restricted SQL","description":"Requires workspace context. Send X-Workspace-Id explicitly, or first call /workspaces/{workspaceId}/select to save the active workspace for this API key. The sql field may contain one or more SELECT, WITH, INSERT, UPDATE, or DELETE statements separated by semicolons. Relation operations: ledger_entries, budget_lines, workspace_settings, and account_metadata support SELECT and, under existing write-approval rules, INSERT, UPDATE, and DELETE; the derived accounts view and global worker-owned fx_rates_raw and fx_rates_daily relations are SELECT-only. Restricted SQL does not support ON CONFLICT. Only SUM, COUNT, MIN, MAX, AVG, and COALESCE function calls are allowed; all other functions are blocked. Use ILIKE instead of LOWER(...) for case-insensitive text search and explicit date ranges instead of NOW() or DATE_TRUNC(). Use regular single-quoted literals. Dollar-quoted strings are not allowed. Results are capped per statement; responses keep rowCount and also include returnedRowCount, totalRowCount, and truncated metadata. Before a long mutating INSERT or UPDATE, first try the same SQL shape on a tiny representative probe: 1-3 literal rows for INSERT or 1 targeted row for UPDATE. The user's explicit approval covers the full approved change set, including that probe and all remaining sequential batches. If the probe fails, 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 records per tool call. Do not pause only to ask the user to continue, proceed, or reconfirm for later batches. Only ask again if the requested change itself changes, new ambiguity appears, or execution fails.","security":[{"ApiKeyHeader":[]}],"parameters":[{"in":"header","name":"X-Workspace-Id","required":false,"schema":{"type":"string"},"description":"Optional after workspace selection. If omitted, the API uses the workspace saved by /workspaces/{workspaceId}/select for this API key. If the key has no saved workspace and the user has exactly one workspace, that workspace is auto-saved and used."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["sql"],"properties":{"sql":{"type":"string","description":"SQL script containing one or more SELECT, WITH, INSERT, UPDATE, or DELETE statements separated by semicolons. Relation operations: ledger_entries, budget_lines, workspace_settings, and account_metadata support SELECT and, under existing write-approval rules, INSERT, UPDATE, and DELETE; the derived accounts view and global worker-owned fx_rates_raw and fx_rates_daily relations are SELECT-only. Only SUM, COUNT, MIN, MAX, AVG, and COALESCE function calls are allowed. Use regular single-quoted literals. Dollar-quoted strings are not allowed."}}}}}},"responses":{"200":{"description":"SQL result envelope with a statements array. Each statement includes rowCount, returnedRowCount, totalRowCount, and truncated."}}}},"/api/agent/send-code":{"post":{"summary":"Start OTP login","description":"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.","responses":{"200":{"description":"OTP challenge created"}}}},"/api/agent/verify-code":{"post":{"summary":"Finish OTP login and mint ApiKey","description":"After a successful OTP verification, return a long-lived ApiKey and instruct the agent to save it outside chat memory, preferably in a local .env file as EXPENSE_BUDGET_TRACKER_API_KEY.","responses":{"200":{"description":"ApiKey issued"}}}}},"components":{"securitySchemes":{"ApiKeyHeader":{"type":"apiKey","in":"header","name":"Authorization","description":"Send the full header value as: ApiKey <key>."}}}}