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_SECRETKEYExternal 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.
| Scope | Covers |
|---|---|
| finance:read | GET on every route group below, plus /dashboard and /reports/{report}. |
| expenses:write | Writes under /expenses, including bulk, split, and void. |
| income:write | Writes under /income. |
| accounts:write | Writes under /accounts, including transactions and balance adjustments. |
| bills:write | Writes under /bills, including templates and payments. |
| loans:write | Writes under /loans, including instance status and payments. |
| credit_cards:write | Writes under /credit-cards, including transactions, statements, and payments. |
| settings:write | Writes under /categories and /user-settings/{app,budget-thresholds}. |
| mcp:connect | Opening an MCP session at /api/mcp. No REST access on its own. |
- A domain write scope also grants
GETon that same domain. /dashboardand/reports/{report}requirefinance:read; no write scope implies them.mcp:connectapplies only to/api/mcpand 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, andCredit Card. Cashcreates only the expense row.Account Debitrequires an activeaccount_idand creates an account debit.Credit Cardrequires an activecredit_card_idand 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
Expenses
Income
Accounts
Categories And Settings
Dashboard And Reports
Credit Cards
Loans
Bills And Recurring
AI Assistant And Telegram
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 /healthto 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, and404distinctly in your client. A403means 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.