Skip to content

Moneybook API

Work with accounts, transactions, imports and workspaces through the API. The OpenAPI contract defines request and response schemas, permissions and error codes.

Servers

  • Local development

    http://localhost:8080
  • Production

    https://spective-backend-1053388195102.asia-south1.run.app

Authentication and access

API contract for Moneybook personal finance platform. Import filenames must be basenames without separators, traversal segments or control characters; invalid names return 400 IMPORT_INVALID_FILENAME. Resource limits: multipart files are limited to 10 MiB each and requests to 20 MiB (SOR documents retain their stricter 5 MiB domain limit). Oversized multipart requests return 413 with UPLOAD_TOO_LARGE (SOR routes use SOR_DOCUMENT_TOO_LARGE). Authenticated /v1 requests have an instance-local fixed-window limit, default 120/minute per verified identity; excess requests return 429 with RATE_LIMITED and Retry-After seconds. Health probes and the separately authenticated scheduler route are excluded. Unexpected server failures return INTERNAL_ERROR with a generic message; internal exception details, database queries, paths and credentials are never returned. Authentication: every operation except health needs either a Supabase Auth access token (`Authorization: Bearer <jwt>`, verified against the project's JWKS, issuer `<SUPABASE_URL>/auth/v1`, audience `authenticated`) or a Moneybook API key (`X-Spective-Api-Key`). Client-supplied identity headers are ignored. Workspaces: data is owned by a workspace. Workspace-scoped operations act on the workspace named by the `X-Spective-Workspace-Id` header (or the `workspaceId` path parameter) and default to the caller's personal workspace. The caller must be an active member; a workspace the caller cannot see answers 404 `WORKSPACE_NOT_FOUND`, a role without the needed permission answers 403 `WORKSPACE_ACCESS_DENIED`. Roles: OWNER (everything, manages members), EDITOR (read, write), AUDITOR (read, export, audit trail), VIEWER (read). GET operations need READ, other methods need WRITE unless stated. API keys are pinned to the workspace they were created in.

Operations

POST /internal/jobs/dispatch

Disabled by default. Requires Google-signed OIDC token from the exact configured scheduler service account, exact audience, verified email and Google issuer. Supabase tokens and API keys are rejected. Claims at most one durable job with the same outcomes as administrator dispatch.

POST /v1/admin/jobs/dispatch

ADMIN bearer-only bounded dispatch of at most one durable job. Disabled unless durable mode and dispatch are enabled. Claims are atomic; repeated requests cannot execute an already claimed job. Expired claims become UNCERTAIN and require operator reconciliation, never automatic replay of financial or notification effects.

POST /v1/sor/statements

Persist immutable statement extraction; verify source ownership, arithmetic and running balances. No ledger writes yet.

GET /v1/sor/statements/{id}

Feature-flagged owner-scoped SOR operation. Requires sor:read or sor:write as appropriate.

POST /v1/sor/statements/{id}/commit

Atomic ledger/provenance/audit commit. Reject stale ledger, overlapping periods and opening mismatch. No guessed opening adjustment or external jobs.

POST /v1/sor/statements/{id}/discard

Feature-flagged owner-scoped SOR operation. Requires sor:read or sor:write as appropriate.

POST /v1/sor/positions

Create immutable owned asset/liability evidence; no guessed inventory or ownership. Same key/payload replays.

GET /v1/sor/positions

Bounded owner-scoped immutable inventory; cursor is the last returned ID. Requires sor:read or sor:write.

GET /v1/sor/positions/{id}

Feature-flagged owner-scoped SOR operation. Requires sor:read or sor:write as appropriate.

POST /v1/sor/valuations

Append dated value evidence. Server records observation time; no provider fetch or FX assumption.

GET /v1/sor/net-worth

Feature-flagged owner-scoped SOR operation. Requires sor:read or sor:write as appropriate.

POST /v1/sor/allocations

Link part of an existing payment/refund to committed bill evidence. Bounds both payment and bill capacity; never posts spend twice.

POST /v1/sor/allocations/{id}/reverse

Feature-flagged owner-scoped SOR operation. Requires sor:read or sor:write as appropriate.

GET /v1/sor/bills/{id}/settlement

Feature-flagged owner-scoped SOR operation. Requires sor:read or sor:write as appropriate.

POST /v1/sor/documents

Experimental feature flag required. Stores up to 5 MiB locally in PostgreSQL; no external extraction.

POST /v1/sor/bills

Validate and persist an immutable draft. Same owner/key/content replays; changed content conflicts. Does not create ledger spend.

GET /v1/sor/bills/{id}

getSorBill

POST /v1/sor/bills/{id}/commit

Confirm the immutable bill evidence and append an audit event atomically. Replays safely; never posts duplicate spending. Payment allocation is a future operation.

POST /v1/sor/classifications

Persist a semantic category proposal with original category and evidence. Does not apply it.

POST /v1/sor/classifications/{id}/{action}

Atomically accept or revert a semantic category assignment, preserving source and audit evidence. Conflicts if the current category changed. Explicitly accepted assignments take precedence over automatic enrichment; new proposals alone never replace them. Reassignment uses a new proposal with the current expectedCategory and explicit acceptance.

GET /v1/wealth/positions

listWealthPosition

POST /v1/wealth/positions

createWealthPosition

GET /v1/wealth/positions/{id}

getWealthPosition

PUT /v1/wealth/positions/{id}

updateWealthPosition

DELETE /v1/wealth/positions/{id}

archiveWealthPosition

GET /v1/wealth/compensation

listWealthCompensation

POST /v1/wealth/compensation

createWealthCompensation

GET /v1/wealth/compensation/{id}

getWealthCompensation

PUT /v1/wealth/compensation/{id}

updateWealthCompensation

DELETE /v1/wealth/compensation/{id}

archiveWealthCompensation

GET /v1/wealth/events

listWealthEvent

POST /v1/wealth/events

createWealthEvent

GET /v1/wealth/events/{id}

getWealthEvent

PUT /v1/wealth/events/{id}

updateWealthEvent

DELETE /v1/wealth/events/{id}

archiveWealthEvent

GET /v1/wealth/scenarios

listWealthScenario

POST /v1/wealth/scenarios

createWealthScenario

GET /v1/wealth/scenarios/{id}

getWealthScenario

PUT /v1/wealth/scenarios/{id}

updateWealthScenario

DELETE /v1/wealth/scenarios/{id}

archiveWealthScenario

GET /v1/wealth/allocations

listWealthAllocation

POST /v1/wealth/allocations

createWealthAllocation

GET /v1/wealth/allocations/{id}

getWealthAllocation

PUT /v1/wealth/allocations/{id}

updateWealthAllocation

DELETE /v1/wealth/allocations/{id}

archiveWealthAllocation

GET /v1/wealth/positions/{id}/valuations

listWealthValuations

POST /v1/wealth/positions/{id}/valuations

appendWealthValuation

GET /v1/wealth/dashboard

getWealthDashboard

POST /v1/wealth/projections

calculateWealthProjection

GET /v1/wealth/projections/{id}

getWealthProjection

GET /v1/health

getHealth

GET /v1/auth/me

Resolves the Supabase identity to the Moneybook user (linking or creating it on first sign-in) and returns it with the personal workspace.

GET /v1/workspaces

Workspaces the caller is an active member of, personal first.

GET /v1/workspaces/{workspaceId}

getWorkspace

GET /v1/workspaces/{workspaceId}/export

OWNER or AUDITOR only. Download a ZIP containing a versioned financial JSON snapshot and original source files. Credentials and statement passwords are excluded. Export fails without returning a partial archive if a source file cannot be read. Maximum archive payload 100 MiB.

POST /v1/workspaces/{workspaceId}/offboarding

Active OWNER bearer session only. Confirmation must equal the workspace id. Deletes all workspace financial rows, memberships, invitations and API keys atomically, retaining only a minimal deletion receipt. Original files are queued for idempotent deletion by the authenticated job dispatcher. The personal workspace is replaced empty on next sign-in; shared workspaces disappear. This does not delete the Supabase identity or other workspaces. Requires exporting first if a copy is wanted; deletion cannot be undone through the app.

GET /v1/workspaces/{workspaceId}/members

Any member may list members.

PATCH /v1/workspaces/{workspaceId}/members/{userId}

OWNER only. Changes role and optional access expiry. Membership changes are serialized per workspace so concurrent requests cannot remove the last active owner. The personal workspace's creator always stays owner. API keys cannot manage membership.

DELETE /v1/workspaces/{workspaceId}/members/{userId}

OWNER user session only; API keys cannot manage membership. The last active owner and the personal workspace's creator cannot be removed.

POST /v1/workspaces/{workspaceId}/leave

Leave a workspace you were invited to. You cannot leave your own personal workspace or leave it without an owner. API keys cannot leave workspaces.

GET /v1/workspaces/{workspaceId}/invites

OWNER only.

POST /v1/workspaces/{workspaceId}/invites

OWNER user session only; API keys cannot manage membership. Creates a pending invite for an email address (valid 14 days). An expired invitation or access grant may be reissued. No email is sent by the API; the invitee sees it under /v1/invites after signing in with that verified email.

DELETE /v1/workspaces/{workspaceId}/invites/{inviteId}

OWNER only. Revokes a pending invite.

GET /v1/workspaces/{workspaceId}/audit-events

Membership and access audit trail. Needs AUDIT (OWNER, AUDITOR).

GET /v1/invites

Pending, unexpired invites addressed to the caller's verified email.

POST /v1/invites/{inviteId}/accept

Accept an invite addressed to the caller's verified email. Both the invitation and any granted access expiry must still be valid. API keys cannot accept invites.

GET /v1/accounts

listAccounts

POST /v1/accounts

createAccount

GET /v1/accounts/{accountId}

getAccount

PATCH /v1/accounts/{accountId}

updateAccount

POST /v1/accounts/{accountId}

POST alias for clients or local environments where PATCH transport is unavailable.

DELETE /v1/accounts/{accountId}

Delete an account and remove all linked transactions, import sessions, import records, and import batches for the authenticated user.

GET /v1/currencies

listEnabledCurrencies

GET /v1/categories

listCategories

POST /v1/categories

createCategory

PATCH /v1/categories/{categoryId}

updateCategory

DELETE /v1/categories/{categoryId}

deleteCategory

GET /v1/imports

listImportSessions

POST /v1/imports

startImport

POST /v1/imports/preview

previewImport

POST /v1/imports/bulk-preview

Validate multiple uploaded statement files with one shared account, format, parser preset, and optional PDF password. Each file creates its own staged import session when accepted.

GET /v1/import-batches

listImportBatches

POST /v1/import-batches/preview

Validate multiple same-format uploaded statement files as one persistent import batch. Each accepted file becomes a child import session retained for source preview and final confirmation.

GET /v1/import-batches/{batchId}

getImportBatch

DELETE /v1/import-batches/{batchId}

Delete an uncommitted import batch and its staged child sessions. Batches with committed transactions cannot be deleted.

GET /v1/import-batches/{batchId}/detail

getImportBatchDetail

POST /v1/import-batches/{batchId}/files

Add more same-format files to an existing staged import batch. Files use the batch account, format, and parser preset. Completed batches are immutable and reject additional files.

POST /v1/import-batches/{batchId}/confirm

confirmImportBatch

POST /v1/import-batches/{batchId}/review

Complete a review-only batch without creating ledger transactions. Intended for batches where every child session has no importable rows.

DELETE /v1/import-batches/{batchId}/sessions/{sessionId}

Remove a staged, uncommitted child session from an import batch.

POST /v1/imports/table-preview

Extract candidate table columns and rows from an upload so users can build parser mappings without writing regex.

GET /v1/imports/{sessionId}

getImportSession

POST /v1/imports/{sessionId}/confirm

confirmImport

POST /v1/imports/{sessionId}/review

Mark a review-only import as completed without creating ledger transactions. Intended for files that were retained for audit/preview but produced no importable rows.

GET /v1/imports/{sessionId}/asset

Return the stored source asset for an import session so users can preview the original file while reviewing parser results.

POST /v1/imports/{sessionId}/asset-preview

Return a browser-previewable source asset. For password-protected PDFs, the supplied one-time password or the account saved password may be used to return an unlocked PDF preview.

GET /v1/imports/{sessionId}/detail

getImportSessionDetail

POST /v1/imports/{sessionId}/corrections

Apply review-time row edits to an uncommitted import session. Completed sessions are immutable and reject corrections.

GET /v1/ops/logs/stream

ADMIN-only development heartbeat stream. Disabled in production; local connections close after 60 seconds.

GET /v1/transactions

listTransactions

POST /v1/transactions

createTransaction

GET /v1/transactions/export

CSV export of the active workspace's ledger. Needs EXPORT (OWNER, AUDITOR); EDITOR and VIEWER get 403.

POST /v1/transactions/bulk

bulkCreateTransactions

GET /v1/transactions/inter-account/link-candidates

Scan for proposed inter-account transfer pairs

GET /v1/transactions/{transactionId}

getTransaction

PATCH /v1/transactions/{transactionId}

updateTransaction

POST /v1/transactions/{transactionId}

POST alias for clients or local environments where PATCH transport is unavailable.

DELETE /v1/transactions/{transactionId}

deleteTransaction

GET /v1/transactions/{transactionId}/inter-account-suggestions

Suggest counterpart transactions for an inter-account transfer link

POST /v1/transactions/{transactionId}/inter-account-link

Link two transactions as an inter-account transfer

DELETE /v1/transactions/{transactionId}/inter-account-link

Remove inter-account transfer link for this transaction

GET /v1/goals

listGoals

POST /v1/goals

createGoal

GET /v1/goals/{goalId}

getGoal

PATCH /v1/goals/{goalId}

updateGoal

DELETE /v1/goals/{goalId}

deleteGoal

GET /v1/insights

listInsights

POST /v1/insights

triggerInsightGeneration

GET /v1/insights/report

getInsightReport

GET /v1/jobs

Latest 200 jobs owned by the active authorized workspace. Unowned historical and global campaign records are excluded. Messages are controlled summaries, never raw exception text.

GET /v1/jobs/{jobId}

Active-workspace job only; foreign or unowned identifiers return 404.

GET /v1/notifications/preferences

getNotificationPreferences

PUT /v1/notifications/preferences

upsertNotificationPreferences

GET /v1/notifications/history

listNotificationHistory

GET /v1/settings/preferences

getUserPreferences

PATCH /v1/settings/preferences

updateUserPreferences

POST /v1/settings/preferences

POST alias for clients or local environments where PATCH transport is unavailable.

POST /v1/settings/reset-data

OWNER only. Deletes all data in the active workspace plus the caller's own notification and preference state.

GET /v1/settings/api-keys

listApiKeys

POST /v1/settings/api-keys

createApiKey

GET /v1/settings/api-keys/{apiKeyId}

getApiKey

PATCH /v1/settings/api-keys/{apiKeyId}

updateApiKey

DELETE /v1/settings/api-keys/{apiKeyId}

revokeApiKey

POST /v1/settings/api-keys/{apiKeyId}/rotate

rotateApiKey

GET /v1/admin/users

Platform admins only (Supabase `app_metadata.role = admin`). Users are created by signing in through Supabase Auth.

PATCH /v1/admin/users/{userId}

Activate or deactivate a user. Roles and credentials are managed in Supabase Auth.

GET /v1/admin/notification-templates

listNotificationTemplates

POST /v1/admin/notification-templates

createNotificationTemplate

GET /v1/admin/notification-templates/{templateId}

getNotificationTemplate

PATCH /v1/admin/notification-templates/{templateId}

updateNotificationTemplate

DELETE /v1/admin/notification-templates/{templateId}

deleteNotificationTemplate

GET /v1/admin/notification-rules

listNotificationRules

POST /v1/admin/notification-rules

createNotificationRule

GET /v1/admin/notification-rules/{ruleId}

getNotificationRule

PATCH /v1/admin/notification-rules/{ruleId}

updateNotificationRule

DELETE /v1/admin/notification-rules/{ruleId}

deleteNotificationRule

POST /v1/admin/notification-campaigns/{campaignId}/trigger

triggerNotificationCampaign

GET /v1/admin/db/parser-presets

listParserPresets

POST /v1/admin/db/parser-presets

createParserPreset

GET /v1/admin/db/parser-presets/{presetId}

getParserPreset

PATCH /v1/admin/db/parser-presets/{presetId}

updateParserPreset

DELETE /v1/admin/db/parser-presets/{presetId}

deleteParserPreset

GET /v1/admin/currencies

listCurrencies

PATCH /v1/admin/currencies/{currencyCode}

updateCurrency

GET /v1/admin/categories

listMasterCategories

POST /v1/admin/categories

createMasterCategory

PATCH /v1/admin/categories/{categoryId}

updateMasterCategory

DELETE /v1/admin/categories/{categoryId}

deleteMasterCategory

GET /v1/admin/db/tables

listDatabaseTables

GET /v1/admin/db/tables/{table}/rows

Active verified ADMIN only. Credential/password/token fields, provider configuration, historical job messages and binary cells are redacted. Page must be nonnegative; size is 1..200.

POST /v1/admin/db/tables/{table}/rows

Insert a row (active verified ADMIN; schema validated and audited)

PATCH /v1/admin/db/tables/{table}/rows

Patch a row (ADMIN; single-row match enforced)

POST /v1/admin/db/tables/{table}/delete

Delete a row (ADMIN; single-row match enforced)

GET /v1/fx-rates

listFxRates