# Moneybook API API contract for Moneybook personal finance platform. Production availability: Wealth APIs are implemented in development; their separate production rollout is pending. 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 `, verified against the project's JWKS, issuer `/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. ## Servers - Local development: http://localhost:8080 - Production: https://spective-backend-1053388195102.asia-south1.run.app ## Contract Use [OpenAPI JSON](https://moneybook.styx.studio/openapi.json) for complete request/response schemas and operation-specific permissions. ## 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 ## Pricing Moneybook is free during early access. Future paid pricing and plan limits have not been announced.