Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.moneda.com/llms.txt

Use this file to discover all available pages before exploring further.

The Moneda REST API endpoints are organized by domain. All endpoints use JSON request/response bodies and require an OAuth 2.0 Bearer token unless noted otherwise.
Write endpoints that involve money — like initiating payments — always require your confirmation in the Moneda app before anything happens.

Base URL

https://api.moneda.com/v1
All endpoints are prefixed with /v1. Include your Bearer token in the Authorization header:
curl -H "Authorization: Bearer YOUR_TOKEN" https://api.moneda.com/v1/balances

Response shape: ?view=full vs ?view=lite

Every GET /v1/* endpoint accepts an optional view query parameter that selects the response shape. This lets LLM agents and terminals consume a compact version of each response without dropping existing integrations.
viewDefaultUse case
fullBack-compat — every field the service exposes. Existing integrations keep working unchanged.
liteSmaller response with optional metadata, coaching fields, derived counts, and empty/null optionals dropped. Designed for LLM agents and CLI rendering.
On a typical support-chatbot turn mix, ?view=lite reduces response payloads by ~20–80% per endpoint (see the per-endpoint notes below). The difference is most dramatic on /v1/wallet, /v1/virtual-accounts, and the balance snapshot-fallback path, where the full shape carries UI coaching text that an LLM reasoning about “how much do I have” doesn’t need.
lite is a strict subset of fields with two renames:
  • balances[].accountNamebalances[].type (on /v1/balances, for semantic clarity)
  • points_activity[].createdAtpoints_activity[].date
Empty arrays, empty objects, null, and "" are also dropped from the response body on every lite call — 0, false, and "0" are preserved (balance of zero is a real answer). This is a minor shape change on lite only; the full shape is unchanged. Example:
# Full (default)
curl https://api.moneda.com/v1/wallet -H "Authorization: Bearer $TOKEN"
# → {"smartAccountAddress":"0x...","network":"Base","supportedTokens":[...],
#    "username":"@alice","warning":"...","tip":"..."}

# Lite
curl 'https://api.moneda.com/v1/wallet?view=lite' -H "Authorization: Bearer $TOKEN"
# → {"smartAccountAddress":"0x...","username":"@alice"}
Routes whose response is already minimal (/v1/health, /v1/exchange-rates, /v1/points/balance, /v1/settings, knowledge search) ignore view because there’s nothing to trim.

Health

GET /v1/health

Server health check. No authentication required. Response
{
  "status": "ok",
  "uptime": 12345,
  "version": "1.0.0"
}

Balances & Finances

GET /v1/balances

Returns your account balances. Scope: read:balances Query parameters
ParameterTypeRequiredDescription
currencyUSD | EUR | CHFNoFilter by currency
viewfull | liteNoResponse shape selector. Default full. See above.
Response (full)
{
  "source": "snapshot",
  "balances": [
    {
      "accountName": "USD Account",
      "currency": "USD",
      "type": "LOCAL",
      "balance": "1250.50"
    },
    {
      "accountName": "EUR Account",
      "currency": "EUR",
      "type": "LOCAL",
      "balance": "890.25"
    }
  ],
  "snapshotTimestamp": "2026-03-07T10:30:00.000Z",
  "snapshotAge": "2 minutes ago"
}
Response (?view=lite) Drops source, snapshotTimestamp, and notice. On the snapshot-fallback path only, collapses all 4 metadata fields to one optional stale field. Renames accountNametype per row.
{
  "balances": [
    { "currency": "USD", "type": "USD Account", "balance": "1250.50" },
    { "currency": "EUR", "type": "EUR Account", "balance": "890.25" }
  ],
  "stale": "2 minutes ago"
}

GET /v1/transactions

Returns your transaction history with powerful filtering. Scope: read:transactions Query parameters
ParameterTypeRequiredDescription
currencyUSD | EUR | CHFNoFilter by currency
typestringNoFilter by exact transaction type (e.g. TRANSFER, SWAP)
typeGroupstringNoFilter by group: transfer, exchange, deposit, withdrawal, earnings, topup, offramp
categorystringNoFilter by spending category (e.g. GROCERIES, TRAVEL)
statusCOMPLETED | PENDING | FAILED | allNoDefault: COMPLETED + COMPLETED_DELAYED. all to include every status.
directionincoming | outgoing | exchangeNoFilter by transaction direction relative to the user
counterpartystringNoSearch by counterparty username, wallet name, address, or external provider
transactionHashstringNoExact hash lookup (with or without 0x prefix, case-insensitive). Returns at most one row.
searchstringNoFree-text substring search over note + reference fields
startDateISO 8601NoFilter from date
endDateISO 8601NoFilter until date
yearnumberNoFilter by year (overrides startDate/endDate)
minAmountnumberNoMinimum amount filter
maxAmountnumberNoMaximum amount filter
hasNotetrue | falseNoFilter by whether transactions have a note
hasCategorytrue | falseNoFilter by whether transactions have a category
sortnewest | oldest | amount_desc | amount_ascNoDefault: newest
limitnumberNoMax 50, default 5
offsetnumberNoPagination offset
viewfull | liteNoResponse shape selector. Default full. See above.
Response (full)
{
  "data": [
    {
      "transactionHash": "0xabc...123",
      "typeGroup": "Transfers",
      "type": "TRANSFER",
      "category": "Friends & Family",
      "note": "Dinner split",
      "direction": "outgoing",
      "amount": "25.00",
      "currency": "USD",
      "status": "COMPLETED",
      "counterparty": {
        "type": "user",
        "username": "@alice"
      },
      "date": "2026-03-06T18:30:00.000Z"
    }
  ],
  "count": 1,
  "limit": 5,
  "offset": 0
}
Exchange transactions include additional fields:
{
  "direction": "exchange",
  "amount": "0",
  "exchanged": "100.00",
  "exchangedCurrency": "USD",
  "received": "92.15",
  "receivedCurrency": "EUR"
}
Counterparty types
TypeFieldsDescription
selfSelf-transfer (swap, deposit, withdrawal)
userusernameMoneda user
providernameService provider (Coinbase, etc.)
bankname, iban, account, routingBank transfer
walletname, addressExternal wallet
unknownaddressUnknown source
Response (?view=lite) Drops typeGroup, type, category, note, reference, exchanged*, received*, and counterparty raw address/iban/account/routing (already masked, still unused by LLM agents). Keeps hash, direction, amount, currency, status, date, and the trimmed counterparty. Per row ≈40–50 % smaller.
{
  "data": [
    {
      "hash": "0xabc...123",
      "direction": "outgoing",
      "amount": "25.00",
      "currency": "USD",
      "status": "COMPLETED",
      "date": "2026-03-06T18:30:00.000Z",
      "counterparty": { "type": "user", "username": "@alice" }
    }
  ],
  "count": 1,
  "limit": 5,
  "offset": 0
}

GET /v1/transactions/{hash}

Single-resource lookup by transaction hash. Scope: read:transactions Path parameters
ParameterTypeDescription
hashstringTransaction hash (with or without 0x prefix, case-insensitive)
Query parameters
ParameterTypeRequiredDescription
viewfull | liteNoDefault full. Pass lite for the compact shape when you don’t need typeGroup/category/note/reference/raw routing.
Returns a single transaction (same shape as the list items above). 404 if not found or not owned by this user.

GET /v1/spending/by-type-group

Spending grouped by transaction type group (formerly /v1/spending/summary). Scope: read:spending Query parameters
ParameterTypeRequiredDescription
period7d | 30d | 90dNoDefault: 30d
currencyUSD | EURNoFilter by currency
Response
{
  "period": "30d",
  "since": "2026-02-05T00:00:00.000Z",
  "typeGroups": [
    {
      "typeGroup": "transfer",
      "name": "Transfers",
      "count": 12,
      "total": "450.00"
    }
  ],
  "totalSpent": "1250.00",
  "transactionCount": 28
}

GET /v1/spending/by-category

Spending grouped by user-assigned category (Groceries, Restaurants, Transport, etc.). Transactions without a category land in an UNCATEGORIZED bucket. Scope: read:spending Query parameters
ParameterTypeRequiredDescription
period7d | 30d | 90dNoDefault: 30d
currencyUSD | EURNoFilter by currency

GET /v1/spending/by-contact

Total amount sent to a single recipient (Moneda user, external bank, or external wallet) over a window. Scope: read:spending Query parameters
ParameterTypeRequiredDescription
accountIdstringYesRecipient account id (from /v1/contacts or /v1/accounts)
period7d | 30d | 90dNoDefault: 30d
currencyUSD | EURNoFilter by currency

GET /v1/spending/by-merchant

Spending grouped by recipient. Each Moneda user gets their own bucket; off-ramps share one bucket per type (bank-withdrawal, external-wallet, coinbase, wallet-connect). Scope: read:spending Query parameters
ParameterTypeRequiredDescription
period7d | 30d | 90dNoDefault: 30d
currencyUSD | EURNoFilter by currency

GET /v1/spending/comparison

Compare spending totals across two arbitrary windows (e.g. this month vs. last month) with absolute and percentage delta. Scope: read:spending Query parameters
ParameterTypeRequiredDescription
currentFromISO dateYesStart of the current period (UTC)
currentToISO dateYesEnd of the current period (UTC)
previousFromISO dateYesStart of the comparison period (UTC)
previousToISO dateYesEnd of the comparison period (UTC)
currencyUSD | EURNoFilter by currency

GET /v1/spending/daily

Daily spending totals for a stablecoin over a rolling window or absolute UTC range. Scope: read:spending Query parameters
ParameterTypeRequiredDescription
currencyUSDC | EURC | CHFAUYesToken to chart
window1d | 1w | 1m | 1yNoRolling window from now (mutually exclusive with from/to)
fromISO dateNoAbsolute window start (UTC)
toISO dateNoAbsolute window end (UTC)

GET /v1/spending/transactions

List the underlying transactions for a spending window (or a category/merchant bucket). Used to drill into a spending breakdown. Scope: read:spending Query parameters
ParameterTypeRequiredDescription
period7d | 30d | 90dNoDefault: 30d
currencyUSD | EURNoFilter by currency
categorystringNoDrill into one category bucket
accountIdstringNoDrill into one merchant/recipient bucket

GET /v1/exchange-rates

Current USD/EUR exchange rate. Scope: read:rates Query parameters
ParameterTypeRequiredDescription
fromUSD | EURNoDefault: USD
toUSD | EURNoDefault: EUR
Response
{
  "from": "USD",
  "to": "EUR",
  "rate": "0.9215",
  "updatedAt": "2026-03-07T10:00:00.000Z"
}

GET /v1/apy-rates

Current earnings APY rates. Scope: read:earnings Query parameters
ParameterTypeRequiredDescription
currencyUSD | EURNoFilter by currency
Response
[
  {
    "product": "Morpho USDC Vault",
    "currency": "USD",
    "apyPercent": "4.25",
    "updatedAt": "2026-03-07T00:00:00.000Z"
  }
]

GET /v1/earnings/time-series

Daily time series of earnings and balance for a Morpho or YO vault account, used for charting yield over time. Scope: read:earnings Query parameters
ParameterTypeRequiredDescription
accountIdstringYesVault account id
window1w | 1m | 3m | 1yNoDefault: 1m
Response
{
  "accountId": "acc_…",
  "window": "1m",
  "points": [
    { "date": "2026-04-14", "balance": "1000.00", "earned": "0.12" }
  ]
}

Account Info

GET /v1/wallet

Your smart wallet address on Base. Scope: read:account Query parameters
ParameterTypeRequiredDescription
viewfull | liteNoDefault full. lite drops network, supportedTokens, warning, tip.
Response (full)
{
  "smartAccountAddress": "0x1234...abcd",
  "network": "Base",
  "supportedTokens": ["USDC", "EURC", "CHFAU"],
  "username": "@john",
  "warning": "Never send tokens on other networks to this address.",
  "tip": "This is your Base smart account address. Only send USDC, EURC, or CHFAU on the Base network."
}
Response (?view=lite) — ~76% smaller
{ "smartAccountAddress": "0x1234...abcd", "username": "@john" }

GET /v1/virtual-accounts

IBAN and ACH virtual account details for receiving transfers. Scope: read:virtual_accounts Query parameters
ParameterTypeRequiredDescription
viewfull | liteNoDefault full. lite drops capabilityInfo, minimumTransfer, firstPartyPayments, thirdPartyPayments, and the per-account type discriminator.
Response (full)
{
  "status": "active",
  "accounts": {
    "eur": {
      "type": "IBAN",
      "iban": "DE89370400440532013000",
      "minimumTransfer": "1.00"
    },
    "usd": {
      "type": "ACH",
      "accountNumber": "1234567890",
      "routingNumber": "021000021",
      "minimumTransfer": "1.00"
    }
  }
}
Status can be active, pending, or not_registered. Response (?view=lite) — ~78% smaller
{
  "status": "active",
  "accounts": {
    "eur": { "iban": "DE89370400440532013000" },
    "usd": { "accountNumber": "1234567890", "routingNumber": "021000021" }
  }
}

GET /v1/accounts

Your saved external wallets and bank accounts. Scope: read:external_accounts Query parameters
ParameterTypeRequiredDescription
viewfull | liteNoDefault full. lite drops walletCount, bankCount, wallet address, wallet provider.
Response (full)
{
  "wallets": [
    {
      "accountId": "acc_123",
      "name": "MetaMask",
      "address": "0xabcd...1234",
      "provider": "EXTERNAL"
    }
  ],
  "bankAccounts": [
    {
      "id": "bank_456",
      "holderName": "John Doe",
      "bankName": "Deutsche Bank",
      "iban": "DE89****3000",
      "country": "DE"
    }
  ],
  "walletCount": 1,
  "bankCount": 1
}

GET /v1/contacts

Your saved contacts (Moneda users, external wallets, and bank accounts). Scope: read:contacts Query parameters
ParameterTypeRequiredDescription
searchstringNoSearch by name, username, or address
limitnumberNoMax results
offsetnumberNoPagination offset
Response
{
  "monedaContacts": [
    {
      "type": "user",
      "username": "@alice",
      "displayName": "Alice Smith",
      "isExternal": false,
      "lastInteraction": "2026-03-06T12:00:00.000Z"
    }
  ],
  "walletContacts": [
    {
      "type": "wallet",
      "contactAccountId": "acc_789",
      "name": "Alice's MetaMask",
      "address": "0xdef...5678"
    }
  ],
  "bankContacts": [
    {
      "type": "bank",
      "contactAccountId": "acc_012",
      "name": "Alice's Bank",
      "bankName": "N26",
      "iban": "DE89****3000",
      "country": "DE"
    }
  ],
  "monedaUsers": 1,
  "wallets": 1,
  "bankAccounts": 1
}

GET /v1/settings

Your app preferences. Scope: read:settings Response
{
  "language": "en",
  "currency": "USD",
  "country": "US",
  "phone": "+1****7890",
  "phoneCountryCode": "+1",
  "theme": "LIGHT",
  "notifications": {
    "email": true,
    "inApp": true
  },
  "biometrics": true,
  "animations": true,
  "decimalSeparator": "DOT",
  "hideBalances": false
}

GET /v1/passkeys

Your authentication passkeys. Scope: read:passkeys Response
{
  "passkeys": [
    {
      "provider": "iCloud Keychain",
      "status": "active",
      "address": "0x12...ab",
      "createdAt": "2026-01-15T10:00:00.000Z"
    }
  ],
  "count": 1
}

GET /v1/recovery-contacts

Your recovery guardians. Scope: read:recovery Response
{
  "myContacts": [
    {
      "username": "@alice",
      "displayName": "Alice Smith",
      "imageUrl": null,
      "status": "accepted",
      "addedAt": "2026-02-01T10:00:00.000Z"
    }
  ],
  "myContactsCount": 1,
  "recoveryContactOf": [
    {
      "username": "@bob",
      "displayName": "Bob Jones",
      "imageUrl": null,
      "status": "accepted",
      "addedAt": "2026-02-15T10:00:00.000Z"
    }
  ],
  "recoveryContactOfCount": 1
}

Recovery emails (ZK email recovery)

The recovery-emails endpoints surface the email addresses set up for ZK email-based account recovery. Mutations (invite, accept, recover) require a passkey-signed UserOp from the mobile app and are not exposed over REST. All four endpoints share the read:recovery scope and accept an optional accountId query parameter — when omitted, the call defaults to the authenticated user’s main LOCAL account.

GET /v1/recovery-emails

List recovery emails set up for the account. Query parameters
ParameterTypeRequiredDescription
accountIdUUIDNoDefaults to the user’s main LOCAL account.
viewfull | liteNoDefault full. lite drops notes and irrelevant timestamps.
Response
{
  "recoveryEmails": [
    {
      "id": "g-abc123",
      "email": "alice@example.com",
      "weight": 100,
      "status": "accepted",
      "invitedAt": "2026-01-01T00:00:00.000Z",
      "acceptedAt": "2026-01-02T00:00:00.000Z",
      "declinedAt": null,
      "removedAt": null,
      "notes": null
    }
  ],
  "count": 1
}
When no recovery emails are configured, the response includes a friendly message field instead of an empty array.

GET /v1/recovery-emails/{id}

Single recovery email lookup by id. 404 if not found or not owned by this user (unified to prevent existence leakage). Path parameters
ParameterTypeDescription
idUUIDRecovery email id (zkRecoveryGuardian.id)
Query parameters
ParameterTypeRequiredDescription
viewfull | liteNoDefault full.
Returns the same shape as the list rows, plus the on-chain accountAddress the email protects.

GET /v1/recovery-config

Recovery configuration for an account: accepted recovery emails, total weight, and the threshold required to approve a recovery. Query parameters
ParameterTypeRequiredDescription
accountIdUUIDNoDefaults to the user’s main LOCAL account.
viewfull | liteNoDefault full. lite adds fullySet: boolean and drops accountAddress + per-row id / acceptedAt.
Response
{
  "accountAddress": "0x1234...abcd",
  "recoveryEmails": [
    {
      "id": "g-abc123",
      "email": "alice@example.com",
      "weight": 100,
      "acceptedAt": "2026-01-02T00:00:00.000Z"
    }
  ],
  "totalWeight": 100,
  "threshold": 100
}
The threshold is a fixed value (currently 100) that must match the on-chain Universal Email Recovery Module configuration. Recovery is approvable when totalWeight >= threshold.

GET /v1/recovery-status

Most recent recovery request snapshot for the account. Reads from the database only — agents tolerate the slight lag while the cron job syncs on-chain state. The mobile app additionally polls on-chain. Query parameters
ParameterTypeRequiredDescription
accountIdUUIDNoDefaults to the user’s main LOCAL account.
viewfull | liteNoDefault full.
Response (no recovery in flight)
{ "hasActiveRecovery": false }
Response (recovery in flight)
{
  "hasActiveRecovery": true,
  "recovery": {
    "id": "r-xyz789",
    "status": "threshold_met",
    "createdAt": "2026-04-01T00:00:00.000Z",
    "executeAfter": "2026-04-02T00:00:00.000Z",
    "executeBefore": "2026-04-09T00:00:00.000Z",
    "threshold": 100,
    "totalRecoveryEmails": 1,
    "approvedCount": 1,
    "approvedWeight": 100,
    "approvedBy": [
      { "email": "a***@example.com", "weight": 100, "approvedAt": "2026-04-01T01:00:00.000Z" }
    ],
    "pending": [],
    "isExecutable": true,
    "timeUntilExecutable": 0,
    "cancelledAt": null,
    "completedAt": null,
    "expiredAt": null
  }
}
hasActiveRecovery is true only for the active states (initiated, threshold_met, executing); terminal states (cancelled, completed, expired, failed) flip it back to false while the latest recovery details are still returned for context. Recovery email addresses are anonymized to first-letter***@domain in the active payload.

Notifications

GET /v1/notifications

Paginated in-app notifications inbox, newest first. Scope: read:notifications Query parameters
ParameterTypeRequiredDescription
cursorUUIDNoId of the last notification from the previous page.
limitnumberNoPage size, max 50, default 20.
filterall | unreadNoDefault all. Pass unread to scope to unread items.
Response
{
  "notifications": [
    {
      "id": "n-abc123",
      "type": "TRANSFER_RECEIVED",
      "deepLink": "moneda://tx/0xabc",
      "metadata": { "amount": "100", "currency": "USD" },
      "isRead": false,
      "createdAt": "2026-04-01T00:00:00.000Z"
    }
  ],
  "nextCursor": "n-abc123"
}
nextCursor is omitted on the last page. metadata shape varies by type — agents should treat it as opaque unless they recognise the type. Mark-as-read / dismiss / clear-all are tied to the active mobile push session and stay in tRPC; the REST surface is read-only.

GET /v1/notifications/unread-count

Unread notification count. Scope: read:notifications Response
{ "count": 7 }

Sub-accounts (vaults)

Sub-accounts (a.k.a. vaults) are nested Safe smart accounts owned by your main account. The endpoints below are read-only — vault create / top-up / withdraw / freeze / close all require a passkey-signed UserOp from the mobile app. All endpoints require the read:sub_accounts scope.

GET /v1/sub-accounts

List the user’s sub-accounts. Query parameters
ParameterTypeRequiredDescription
includeClosedbooleanNoDefault false. Pass true to include archived vaults.
viewfull | liteNoDefault full.
Response
{
  "subAccounts": [
    {
      "id": "sa-abc123",
      "name": "Travel fund",
      "status": "ACTIVE",
      "type": "VAULT",
      "address": "0xchild...safe",
      "createdAt": "2026-02-01T10:00:00.000Z"
    }
  ]
}

GET /v1/sub-accounts/{id}

Single sub-account detail by id. Path parameters
ParameterTypeDescription
idUUIDSub-account id
Returns the full sub-account shape including parent + child Safe addresses, members, deployment index, and close timestamp.

GET /v1/sub-accounts/{id}/balance

On-chain balances for a sub-account’s child Safe. Path parameters
ParameterTypeDescription
idUUIDSub-account id
Query parameters
ParameterTypeRequiredDescription
currencyUSDC | EURC | CHFAUNoFilter to a single token. Omit for every enabled currency.
viewfull | liteNoDefault full.
Balances are returned as raw bigint strings (6 decimals).

GET /v1/wealth

Aggregate wealth across the main account + every live vault. Scope: read:balances + read:sub_accounts. Query parameters
ParameterTypeRequiredDescription
viewfull | liteNoDefault full.
Returns per-leg breakdown (wallet + Morpho + YO for the main account; wallet-only for vaults) plus per-currency totals. USDC and EURC are cross-converted using the on-chain Aerodrome rate.

Sessions

API key authentication produces sessions tracked in the database for audit trails. Bearer-token (OAuth) requests don’t create sessions.

POST /v1/sessions

Create a session record (typically called by clients that hold long-lived API keys). Scope: depends on the calling channel. Request body
{
  "deviceName": "moneda-cli",
  "platform": "darwin"
}
Response
{
  "sessionId": "ses_abc123",
  "createdAt": "2026-04-01T00:00:00.000Z"
}

DELETE /v1/sessions/{sessionId}

End a session. Path parameters
ParameterTypeDescription
sessionIdUUIDSession id from the create response
Returns 204 No Content on success.

Points & Referrals

GET /v1/points/balance

Your points balance. Scope: read:points Response
{
  "totalPoints": 1500
}

GET /v1/points/activity

Points earning history. Scope: read:points Query parameters
ParameterTypeRequiredDescription
limitnumberNoMax results
offsetnumberNoPagination offset
Response
{
  "data": [
    {
      "actionType": "REFERRAL_BONUS",
      "points": 500,
      "description": "Referred a friend",
      "createdAt": "2026-03-01T10:00:00.000Z"
    }
  ],
  "count": 1,
  "limit": 10,
  "offset": 0
}

GET /v1/referrals/code

Your referral link and stats. Scope: read:referrals
First call for a new user lazily enrolls them into GrowSurf (get-or-create). Subsequent calls are pure reads.
Response
{
  "code": "abc123",
  "link": "https://moneda.com/ref?grsf=abc123",
  "referralCount": 5
}

GET /v1/referrals/referees

Friends you’ve referred. Scope: read:referrals Response
{
  "referees": [
    {
      "name": "Alice",
      "email": "",
      "status": "referred",
      "referredAt": "2026-02-20T10:00:00.000Z"
    }
  ],
  "count": 1
}

Knowledge Base

These endpoints are public — no authentication required.

GET /v1/knowledge/search

Search the FAQ knowledge base. Query parameters
ParameterTypeRequiredDescription
querystringNoSearch query
categorystringNoFilter by category slug
limitnumberNoMax results
Response
{
  "query": "transfer fees",
  "totalMatches": 3,
  "returned": 3,
  "results": [
    {
      "id": "faq_001",
      "question": "Are there fees for transfers?",
      "answer": "Moneda does not charge fees for transfers between Moneda users...",
      "category": "Transfers"
    }
  ]
}

GET /v1/knowledge/categories/

Browse a FAQ category by slug. Path parameters
ParameterTypeRequiredDescription
slugstringYesCategory slug (e.g. transfers, security)
Response
{
  "category": "Transfers",
  "slug": "transfers",
  "itemCount": 8,
  "items": [
    {
      "id": "faq_001",
      "question": "Are there fees for transfers?",
      "answer": "...",
      "category": "Transfers"
    }
  ]
}

GET /v1/knowledge/items/

Get a specific FAQ item. Path parameters
ParameterTypeRequiredDescription
idstringYesFAQ item ID
Response
{
  "id": "faq_001",
  "question": "Are there fees for transfers?",
  "answer": "Moneda does not charge fees for transfers between Moneda users...",
  "category": "Transfers",
  "categorySlug": "transfers",
  "relatedItems": [
    {
      "id": "faq_002",
      "question": "How long do transfers take?",
      "category": "Transfers"
    }
  ]
}

Users

GET /v1/users/search

Search Moneda users by username or display name. Scope: read:users Query parameters
ParameterTypeRequiredDescription
querystringYesSearch query
Response
{
  "users": [
    {
      "username": "@alice",
      "displayName": "Alice Smith"
    }
  ],
  "count": 1
}

Write Endpoints

PATCH /v1/profile

Update your display name. Scope: write:profile Request body
{
  "displayName": "John Doe"
}
Response
{
  "success": true
}

PATCH /v1/transactions/{hash}/category

Assign a spending category to a transaction. Scope: write:transactions Path parameters
ParameterTypeRequiredDescription
hashstringYesTransaction hash
Request body
{
  "category": "GROCERIES"
}
Valid categories: TOP_UP, INVESTMENT, FRIENDS_FAMILY, INTEREST_EARNINGS, SUBSCRIPTIONS, SERVICES, GROCERIES, SHOPPING, RESTAURANTS, TRANSPORT, TRAVEL, UTILITIES, CASH, SALARY, FUEL, EV_CHARGING, GAMBLING, CHARITY_DONATIONS, TAXES, INSURANCE, CARD, HEALTHCARE, GENERAL Response
{
  "success": true
}

PATCH /v1/transactions/{hash}/note

Add or update a note on a transaction. Scope: write:transactions Path parameters
ParameterTypeRequiredDescription
hashstringYesTransaction hash
Request body
{
  "note": "Dinner at Luigi's"
}
Response
{
  "success": true
}

POST /v1/transactions/batch/categories

Categorize up to 25 transactions at once. Scope: write:transactions Request body
{
  "updates": [
    { "transactionHash": "0xabc...123", "category": "GROCERIES" },
    { "transactionHash": "0xdef...456", "category": "TRAVEL" }
  ]
}
Response
{
  "succeeded": 2,
  "failed": 0
}

POST /v1/transactions/batch/notes

Add notes to up to 25 transactions at once. Scope: write:transactions Request body
{
  "updates": [
    { "transactionHash": "0xabc...123", "note": "Weekly groceries" },
    { "transactionHash": "0xdef...456", "note": "Flight to Paris" }
  ]
}
Response
{
  "succeeded": 2,
  "failed": 0
}

POST /v1/accounts/wallets

Save an external crypto wallet. Scope: write:external_accounts Request body
{
  "walletName": "MetaMask",
  "walletAddress": "0x1234567890abcdef1234567890abcdef12345678",
  "confirmed": true
}
Set confirmed: false first to check for warnings (e.g. if the address belongs to another Moneda user). Then resend with confirmed: true to proceed. Response
{
  "success": true,
  "wallet": {
    "id": "acc_123",
    "name": "MetaMask",
    "address": "0x1234...5678",
    "network": "Base",
    "supportedTokens": ["USDC", "EURC"]
  }
}
Or if confirmation is needed:
{
  "success": false,
  "requiresConfirmation": true,
  "message": "This address belongs to a Moneda user. Are you sure you want to add it as an external wallet?"
}

POST /v1/accounts/banks

Save a bank account (EU IBAN or US ACH). Scope: write:external_accounts Request body (EU)
{
  "firstName": "John",
  "lastName": "Doe",
  "country": "DE",
  "iban": "DE89370400440532013000",
  "bic": "COBADEFFXXX",
  "confirmed": true
}
Request body (US)
{
  "firstName": "John",
  "lastName": "Doe",
  "country": "US",
  "accountNumber": "1234567890",
  "routingNumber": "021000021",
  "accountType": "CHECKING",
  "confirmed": true
}
Response
{
  "success": true,
  "bankAccount": {
    "id": "bank_456",
    "holderName": "John Doe",
    "country": "DE",
    "iban": "DE89****3000"
  }
}

POST /v1/payments

Initiate a payment. Requires approval in the Moneda app. Scope: write:payments
Payments are not executed immediately. After calling this endpoint, the user must approve the payment in the Moneda mobile app within 5 minutes.
Request body
{
  "paymentType": "transfer_moneda",
  "amount": "50.00",
  "currency": "USD",
  "recipientUsername": "@alice",
  "reference": "Dinner split"
}
Payment types
TypeRequired fieldsDescription
transfer_monedarecipientUsernameSend to a Moneda user
transfer_accountaccountIdSend to a saved external account
transfer_contact_bankcontactUsername, contactAccountIdSend to a contact’s bank account
transfer_contact_walletcontactUsername, contactAccountIdSend to a contact’s wallet
Response
{
  "paymentRequestId": "pr_abc123",
  "status": "PENDING_APPROVAL",
  "amount": "50.00",
  "currency": "USD",
  "recipient": "@alice",
  "paymentType": "transfer_moneda",
  "expiresAt": "2026-03-07T10:35:00.000Z",
  "message": "Payment request created. Please approve in your Moneda app within 5 minutes.",
  "nextStep": "Open the Moneda app to approve this payment.",
  "pollAfterMs": 3000
}

GET /v1/payments//status

Check the status of a payment request. Scope: write:payments Path parameters
ParameterTypeRequiredDescription
idstringYesPayment request ID (UUID)
Response
{
  "paymentRequestId": "pr_abc123",
  "status": "COMPLETED",
  "amount": "50.00",
  "currency": "USD",
  "recipient": "@alice",
  "paymentType": "transfer_moneda",
  "expiresAt": "2026-03-07T10:35:00.000Z",
  "message": "Payment completed successfully.",
  "transactionHash": "0xabc...123"
}
Payment statuses: PENDING_APPROVAL, APPROVED, COMPLETED, FAILED, EXPIRED, REJECTED

Payment Routing

These endpoints help an agent or client choose a payment corridor and quote the cost before calling POST /v1/payments. They are provider-agnostic — don’t branch on which underlying processor handles each rail.

GET /v1/payment-destinations

List the country, currency, and rail combinations Moneda can pay out to. Each entry includes the required fields you’ll need to collect from the user before calling POST /v1/payments. Scope: read:external_accounts Query parameters
ParameterTypeRequiredDescription
countrySymbolstringNoFilter by ISO country code (e.g. BR, MX, CO)

GET /v1/payment-quote

Preview the cost, FX rate, and final amount for moving money between two currencies. Provide exactly one of amountIn (what the user sends) or amountOut (what they want to receive). Scope: read:rates Query parameters
ParameterTypeRequiredDescription
fromCurrencystringYesSource currency (e.g. USDC, EUR)
toCurrencystringYesTarget currency
amountInstringOne ofAmount the sender pays (mutually exclusive with amountOut)
amountOutstringOne ofAmount the recipient receives (mutually exclusive with amountIn)

Receipts

Receipts are user-uploaded files (PDFs, images, structured invoices) the platform OCRs, extracts to structured fields, and optionally auto-matches to a transaction. Bills are the invoice-shaped sibling (AP queue) covered separately below.

GET /v1/receipts

List the user’s transaction receipts, newest first, with extracted summary fields and lifecycle status. Scope: read:receipts Query parameters: transactionHash (optional), needsReview (true/false, optional), status (optional), limit (default 20, max 100), cursor (optional).

GET /v1/receipts/{receiptId}

Get one receipt with full extracted fields and a presigned S3 download URL valid for ~15 minutes. Scope: read:receipts

GET /v1/receipts/{receiptId}/line-items

List parsed line items on a receipt — description, quantity, unit price, total, tax rate, category. Scope: read:receipts

POST /v1/receipts/{receiptId}/line-items

Add a line item to a receipt. Used after manual extraction review. Scope: write:receipts

PATCH /v1/receipts/line-items/{lineItemId}

Update one line item on a receipt (description, quantity, price, tax rate, category). Scope: write:receipts

DELETE /v1/receipts/line-items/{lineItemId}

Remove one line item from a receipt. Scope: write:receipts

GET /v1/receipts/line-items/search

Search across every line item the user has uploaded (case-insensitive substring on description). Returns matching items + aggregate spend grouped by currency. Optional category and date-range filters. Scope: read:receipts

POST /v1/receipts

Create a receipt row (metadata only; for direct upload use the upload-url + finalize flow). Scope: write:receipts

DELETE /v1/receipts/{receiptId}

Delete a receipt and its line items. Scope: write:receipts

POST /v1/receipts/upload-url

Step 1 of 2 — get a presigned S3 PUT URL to stage a local file. Returns uploadUrl, uploadKey, and constraints (size cap, content-type whitelist). The file bytes go directly to S3. Scope: write:receipts

POST /v1/receipts/finalize

Step 2 of 2 — after PUTting the file to S3, finalize the receipt to run OCR + LLM extraction, attempt auto-match to a transaction, and persist line items. Pass the uploadKey from /v1/receipts/upload-url. Scope: write:receipts

POST /v1/receipts/import-url

Import a receipt directly from an https:// URL (S3, Drive shared link, public file host). The server fetches behind an SSRF guard — rejects private IPs and non-https. Single call. Scope: write:receipts

POST /v1/receipts/{receiptId}/attach

Link a receipt to a transaction (used after manual review when auto-match didn’t run or was wrong). Scope: write:receipts

POST /v1/receipts/{receiptId}/detach

Unlink a receipt from its transaction. Scope: write:receipts

POST /v1/receipts/{receiptId}/confirm-suggestion

Accept the auto-match suggestion the OCR pipeline proposed. Scope: write:receipts

POST /v1/receipts/{receiptId}/reject-suggestion

Reject the auto-match suggestion (the receipt stays unlinked). Scope: write:receipts

Bills (AP queue)

Bills are the invoice-shaped sibling of receipts. They surface as an accounts-payable queue ordered due-soon-first, and carry merchant + payment routing detail extracted by OCR.

GET /v1/bills

List the user’s bills (AP queue), ordered dueDate ASC NULLS LAST, id DESC. Cursor-paginated; defaults to status=PENDING. Scope: read:receipts

GET /v1/bills/{billId}

Get one bill with full extracted detail — merchant (name, tax id, address), total, currency, due date, invoice number, vendor IBAN/BIC/routing/account, payment status, presigned download URL. Scope: read:receipts

Bewirtungsbelege (German hospitality receipts)

Bewirtungsbelege are an overlay on existing transactions that capture German tax-deductible business-meal detail (occasion, attendees, deductible/non-deductible split). They reference a transaction; the receipt file itself stays in the Receipts table.

GET /v1/bewirtungsbelege

List the user’s Bewirtungsbelege. Each row carries the underlying transaction, date, location, occasion, total/tip/subtotal split, deductible/non-deductible amounts, and signing state. Scope: read:receipts

GET /v1/bewirtungsbelege/{bewirtungsbelegId}

Get one Bewirtungsbeleg with full detail including attendees. Scope: read:receipts

POST /v1/bewirtungsbelege

Create a Bewirtungsbeleg overlay for an existing transaction. Scope: write:receipts

DELETE /v1/bewirtungsbelege/{bewirtungsbelegId}

Delete a Bewirtungsbeleg overlay. The underlying transaction and receipt file are untouched. Scope: write:receipts

Eigenbelege (German self-issued substitute receipts)

Eigenbelege are used when the original receipt for a transaction was lost or never issued. They capture the recipient, amount, and reason for the substitute receipt.

GET /v1/eigenbelege

List the user’s Eigenbelege. Each row carries the linked transaction, recipient name, amount, reason, and signing state. Scope: read:receipts

GET /v1/eigenbelege/{eigenbelegId}

Get one Eigenbeleg with full detail. Scope: read:receipts

POST /v1/eigenbelege

Create an Eigenbeleg overlay for an existing transaction. Scope: write:receipts

DELETE /v1/eigenbelege/{eigenbelegId}

Delete an Eigenbeleg overlay. The underlying transaction is untouched. Scope: write:receipts

Cards

Card endpoints return PCI-redacted metadata only — last four digits, expiry, design, status. Full PAN / CVV / PIN are never available via the API. Freeze and unfreeze run on-chain via the PausableCardGuard and are fully reversible.

GET /v1/cards

List the user’s cards (active, suspended, expired, terminated) with PCI-redacted metadata. Scope: read:cards

GET /v1/cards/{cardId}

Get detailed metadata for one card (status, type, program, currency, expiry, design). Returns NOT_FOUND if the card does not exist or does not belong to the caller. Scope: read:cards

POST /v1/cards/{cardId}/freeze

Freeze a card on-chain via the PausableCardGuard. Settlement is blocked at the Roles Module level. Idempotent at the storage level (re-freezing succeeds, but a fresh on-chain tx is submitted on every call). Returns CONFLICT if the card’s on-chain proxy hasn’t been deployed yet. Scope: write:cards

POST /v1/cards/{cardId}/unfreeze

Unfreeze a card on-chain via the PausableCardGuard. Restores settlement. Idempotent at the storage level. Scope: write:cards

Scheduled (Recurring) Transactions

Read + lifecycle (pause/resume) endpoints for scheduled transactions. Create / activate / cancel / edit / renew are NOT exposed via REST — they need a passkey signature on a Smart Sessions install UserOp, which is mobile-app only.

GET /v1/scheduled-transactions

List the user’s scheduled (recurring) transactions. Filter by status. Scope: read:transactions

GET /v1/scheduled-transactions/{id}

Get one scheduled transaction by id. Scope: read:transactions

GET /v1/scheduled-transactions/upcoming

List the next scheduled executions across all the user’s schedules. Scope: read:transactions

GET /v1/scheduled-transactions/{id}/history

List past executions for one schedule. Scope: read:transactions

POST /v1/scheduled-transactions/{id}/pause

Pause execution of a schedule (backend-only state flip; no on-chain change). Scope: write:transactions

POST /v1/scheduled-transactions/{id}/resume

Resume execution of a paused schedule. Scope: write:transactions

Error Responses

All errors follow a consistent format:
{
  "code": "UNAUTHORIZED",
  "message": "Invalid or missing Bearer token",
  "retryable": false
}
Error codes
CodeHTTP StatusDescription
UNAUTHORIZED401Invalid or missing Bearer token
INSUFFICIENT_SCOPE403Token lacks the required scope
NOT_FOUND404Resource not found
RESOURCE_NOT_OWNED403Resource belongs to another user
VALIDATION_ERROR422Invalid request parameters
RATE_LIMIT_EXCEEDED429Too many requests (60/min per IP)
INTERNAL_ERROR500Unexpected server error
SERVICE_UNAVAILABLE503Downstream service temporarily unavailable

What’s next?

Authentication

Learn how to authenticate your API requests.

OpenAPI Spec

Explore the API interactively with Swagger UI.

Scopes

See which scopes each endpoint requires.

CLI Tool

Use these endpoints from the command line with the Moneda CLI.