REST API

Integrate DoubleM through predictable HTTP endpoints.

The DoubleM REST API exposes user-scoped finance records, reports, settings, AI parsing, card statement flows, bills, loans, and payment actions under a versioned JSON API.

What Is A REST API?

A REST API is an HTTP interface where each URL represents a resource and each HTTP method describes the action to take. DoubleM uses REST-style routes such as /expenses, /accounts, and /credit-cards/soa. Clients send JSON or multipart form data, authenticate with a bearer token, and receive JSON responses.

Use GET to read

List records, fetch one record, read summaries, load reports, and populate form options.

Use POST to create or act

Create expenses, income, accounts, cards, payments, AI parse requests, splits, and balance adjustments.

Use PUT or PATCH to update

PUT is used for broad updates. PATCH is used where the route changes a specific state, such as archive or loan status.

Use DELETE to remove

Delete records where supported. Accounts and credit cards are soft-archived, preserving historical transactions.

Base URL And Interactive Docs

REST API base URL: https://finance-tracker.0xdd.cloud/api/v1

Use the shared API contract as the source of truth for behavior. The app also exposes Swagger UI and the raw OpenAPI YAML as an interactive companion for request shapes and examples.

Authentication

Send credentials with every API request:

Authorization: Bearer dmk_PUBLICKEY_SECRETKEY

External integrations use a user-generated API key token in the format dmk_<public_key>_<secret_key>. Users create and revoke API keys from DoubleM settings, where the token is shown once at creation. API-key access is available to Pro users. The same key authenticates the MCP server.

Keys issued in the retired <public_key>.<secret_key> format no longer authenticate and return 401 UNAUTHORIZED. Create a replacement key in settings and update your integration.

First-party clients may use a Supabase JWT bearer token, which is not scope-limited. Password changes require Supabase JWT auth and should not be attempted with API-key auth. Telegram webhook routes use a Telegram secret header and are not part of normal third-party API-key access.

Scopes

Each API key carries an explicit scope list. A request is rejected with 403 FORBIDDEN when the key is missing the scope for that route.

ScopeCovers
finance:readGET on every route group below, plus /dashboard and /reports/{report}.
expenses:writeWrites under /expenses, including bulk, split, and void.
income:writeWrites under /income.
accounts:writeWrites under /accounts, including transactions and balance adjustments.
bills:writeWrites under /bills, including templates and payments.
loans:writeWrites under /loans, including instance status and payments.
credit_cards:writeWrites under /credit-cards, including transactions, statements, and payments.
settings:writeWrites under /categories and /user-settings/{app,budget-thresholds}.
mcp:connectOpening an MCP session at /api/mcp. No REST access on its own.
  • A domain write scope also grants GET on that same domain.
  • /dashboard and /reports/{report} require finance:read; no write scope implies them.
  • mcp:connect applies only to /api/mcp and grants no REST access on its own.
  • Selecting every scope stores all, which grants everything.

Response Envelope And Errors

Every route returns a consistent envelope so clients can centralize success and error handling.

// Success
{
  "success": true,
  "message": "Success",
  "data": {}
}

// Error
{
  "success": false,
  "error": {
    "message": "Missing or invalid Authorization header",
    "code": "UNAUTHORIZED"
  }
}

Canonical error codes include UNAUTHORIZED, RATE_LIMIT_EXCEEDED, VALIDATION_ERROR, INVALID_STATUS, NOT_FOUND, SUBSCRIPTION_REQUIRED, and INTERNAL_SERVER_ERROR.

Pagination, Sorting, And Filtering

List endpoints use shared pagination and sorting query parameters.

GET /expenses?page=1&limit=20&sortBy=date&sortOrder=desc

{
  "success": true,
  "message": "Success",
  "data": {
    "items": [],
    "pagination": {
      "page": 1,
      "limit": 20,
      "total_items": 150,
      "total_pages": 8
    }
  }
}

Standard parameters are page, limit, sortBy, and sortOrder. Many list routes add filters such as status, dateFrom, dateTo, search, cardId, category, or payment_method.

Core Business Rules

  • All data is owner-scoped. Do not send or trust client-provided user IDs.
  • Money amounts for transactional writes must be positive numbers.
  • Dates use YYYY-MM-DD.
  • New expense payment methods are Cash, Account Debit, and Credit Card.
  • Cash creates only the expense row.
  • Account Debit requires an active account_id and creates an account debit.
  • Credit Card requires an active credit_card_id and creates a card transaction.
  • AI parse endpoints only return parsed expense objects. They do not persist records.
  • Maria AI assistant write flows are confirm-first and create side effects only after explicit linking.

Common Examples

Create a cash expense:

curl -X POST "https://finance-tracker.0xdd.cloud/api/v1/expenses" \
  -H "Authorization: Bearer dmk_PUBLICKEY_SECRETKEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 450,
    "date": "2026-05-10",
    "description": "Lunch at Jollibee",
    "category": "Food",
    "payment_method": "Cash"
  }'

Create an account-debit expense with a side effect:

curl -X POST "https://finance-tracker.0xdd.cloud/api/v1/expenses" \
  -H "Authorization: Bearer dmk_PUBLICKEY_SECRETKEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 1200,
    "date": "2026-05-10",
    "description": "Internet bill",
    "category": "Utilities",
    "payment_method": "Account Debit",
    "account_id": "ACCOUNT_UUID"
  }'

Parse natural language without saving:

curl -X POST "https://finance-tracker.0xdd.cloud/api/v1/expenses/ai/parse-text" \
  -H "Authorization: Bearer dmk_PUBLICKEY_SECRETKEY" \
  -H "Content-Type: application/json" \
  -d '{ "natural_text": "Spent 200 on Grab and 150 on lunch yesterday" }'

Use JavaScript fetch:

const response = await fetch("https://finance-tracker.0xdd.cloud/api/v1/dashboard", {
  headers: {
    Authorization: `Bearer ${process.env.DOUBLEM_API_TOKEN}`,
    Accept: "application/json"
  }
})

const payload = await response.json()
if (!payload.success) {
  throw new Error(payload.error.message)
}

Endpoint Catalog

System

GET /health

Expenses

GET /expenses
POST /expenses
GET /expenses/{id}
PUT /expenses/{id}
DELETE /expenses/{id}
POST /expenses/{id}/split
POST /expenses/bulk
GET /expenses/form-options
GET /expenses/summary
POST /expenses/ai/parse-text
POST /expenses/ai/parse-images

Income

GET /income
POST /income
GET /income/{id}
PUT /income/{id}
DELETE /income/{id}
GET /income/form-options

Accounts

GET /accounts
POST /accounts
GET /accounts/{id}
PUT /accounts/{id}
DELETE /accounts/{id}
PATCH /accounts/{id}/archive
PATCH /accounts/{id}/unarchive
GET /accounts/transactions
GET /accounts/{id}/transactions
POST /accounts/{id}/transactions
PUT /accounts/{id}/transactions/{transactionId}
DELETE /accounts/{id}/transactions/{transactionId}
POST /accounts/{id}/balance-adjustment

Categories And Settings

GET /categories
POST /categories
GET /categories/{id}
PUT /categories/{id}
DELETE /categories/{id}
GET /user-settings/account
PUT /user-settings/account/profile-picture
PUT /user-settings/account/password
GET /user-settings/app
PUT /user-settings/app
GET /user-settings/budget-thresholds
PUT /user-settings/budget-thresholds
GET /user-settings/categories
POST /user-settings/categories
GET /user-settings/categories/{id}
PUT /user-settings/categories/{id}
DELETE /user-settings/categories/{id}

Dashboard And Reports

GET /dashboard
GET /reports/{report}

Credit Cards

GET /credit-cards
POST /credit-cards
GET /credit-cards/{id}
PUT /credit-cards/{id}
PATCH /credit-cards/{id}/archive
PATCH /credit-cards/{id}/unarchive
DELETE /credit-cards/{id}
GET /credit-cards/{id}/unbilled-transactions
GET /credit-cards/transactions
POST /credit-cards/transactions
GET /credit-cards/transactions/{id}
PUT /credit-cards/transactions/{id}
DELETE /credit-cards/transactions/{id}
GET /credit-cards/soa
GET /credit-cards/soa/counts
GET /credit-cards/soa/{id}
PUT /credit-cards/soa/{id}
GET /credit-cards/soa/{id}/payments
POST /credit-cards/soa/{id}/payments
POST /credit-cards/soa/{id}/transactions
POST /credit-cards/soa/{id}/transactions/link
POST /credit-cards/soa/{id}/void
GET /credit-cards/logs
GET /credit-cards/metrics
GET /credit-cards/usage-summary
GET /credit-cards/form-options
POST /credit-cards/transactions/{id}/convert-to-installment
GET /credit-cards/installment-plans
GET /credit-cards/installment-plans/{id}
POST /credit-cards/installment-plans/{id}/cancel

Loans

GET /loans
POST /loans
GET /loans/{id}
PATCH /loans/{id}
DELETE /loans/{id}
GET /loans/{id}/instances
GET /loans/current-month-dues
PATCH /loans/instances/{instanceId}/status
POST /loans/instances/{instanceId}/payments

Bills And Recurring

GET /bills
GET /bills/summary
GET /bills/form-options
PUT /bills/{id}
POST /bills/{id}/status
GET /bills/{id}/payments
POST /bills/{id}/payments
DELETE /bills/payments/{paymentId}
GET /bills/templates
POST /bills/templates
GET /bills/templates/{id}
PUT /bills/templates/{id}
DELETE /bills/templates/{id}

AI Assistant And Telegram

POST /ai-assistant/chat
POST /ai-assistant/confirm
POST /telegram/webhook

Integration Checklist

  • Create an API key in DoubleM settings and store only the full bearer token in your server environment.
  • Grant the narrowest scopes your integration needs. You can always issue a second key rather than widening the first.
  • Call GET /health to verify connectivity. It is the one route any authenticated key can reach regardless of scope.
  • Use form-option routes before write forms so users select valid accounts, cards, categories, and payment sources.
  • Handle 401, 402, 403, 429, 400, and 404 distinctly in your client. A 403 means a missing scope, not a missing record.
  • Keep write operations idempotent in your own app where possible. Avoid blind retries for payments or balance adjustments.
  • Use /reports/{report}, /dashboard, and summary routes for read-heavy dashboards instead of re-aggregating all raw records.
  • Review the interactive Swagger docs after each backend release for schema additions.