REST API and OAuth

The public API at api.taz.randomfact.com: authentication, endpoints, and the OpenAPI spec.

Everything the app does, it does through a public REST API. The web app is one client of it; the MCP server is another; you can be a third.

Base URL: https://api.taz.randomfact.com

OpenAPI spec: /v1/openapi.json — no authentication needed to read it.

Interactive docs: /docs/ — Swagger UI, with a sign-in button that walks you through OAuth so you can try calls in the browser.

The spec is generated from the same schemas that validate requests at runtime, so it can't drift from what the server actually accepts.

Authenticating

Every endpoint takes Authorization: Bearer <token>, and there are two kinds of token.

OAuth 2.0 with PKCE is the path for anything you build. Register nothing by hand — the flow is standard, discovery lives at the base URL, and the resulting access token is checked on every request, so revoking access takes effect immediately.

An OAuth-authorized request is pinned to your personal workspace. It cannot be pointed at a shared workspace, and any workspace header it sends is ignored. See Workspaces and households.

Firebase ID tokens are what the web app itself uses, and they're what makes shared workspaces reachable — those requests name a workspace and are membership-checked against it.

Anonymous access is rejected everywhere. There are no unauthenticated data endpoints.

What's there

Roughly, by area:

  • Transactions — list (cursor-paginated, filterable), summarize, stream the whole corpus, export and import a bundle, override a category, soft-delete and undelete.
  • Categories — list (optionally with their rules), create, rename, delete.
  • Rules — list, create, update, delete, bulk delete, reapply, test a rule against your transactions without saving it, and list what a saved rule currently accounts for.
  • Duplicates — start a scan, dismiss a cluster.
  • Reports — the uncategorized report with a reason per row.
  • Workspaces — list your memberships, create, rename, delete, manage members and invitations.
  • Uploads — post a statement (multipart, 5 MB), and the CSV mapping flow.

Read the spec for the exact shapes; it's the authority, and this list will age faster than it does.

Filtering

Most read endpoints accept a query parameter in a small filter language — the same one the app itself evaluates. /docs/query-language covers it clause by clause:

category eq 'Groceries' and debit ge 50 and transactionDate ge 20240101

Server-side, a query is split into the part a database index can answer and the part that has to be evaluated in memory, and both halves run through the same evaluator the browser uses. That's on purpose: it means the API and the app can't disagree about what a filter means.

Results are capped. A truncated: true in the response means the cap was hit and you should narrow the query rather than assume you have everything.

Things worth knowing

Rule edits are asynchronous. Creating, editing, or deleting a rule returns as soon as the rule is written; re-checking the transactions it affects happens in the background. Don't read back immediately and conclude nothing happened.

Uploads are asynchronous too. POST /transactions/upload returns 202 with a file id; watch the file's record for parsed, unsupported, or failed.

The stream endpoint supports deltas. GET /transactions/stream returns newline-delimited JSON. Pass since= with the watermark from the previous response's header and you get only what has changed since, which is how the app stays current without re-downloading your history.

Transaction ids are deterministic, derived from date, description, and amounts. Writing the same transaction twice is idempotent by construction.

Admin endpoints exist under /api/admin/, gated on a verified email address. OAuth tokens can never reach them — they carry no email claim.