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 provides 43 endpoints 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/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"
  }
]

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//category

Assign a spending category to a transaction. Scope: write:transactions Path parameters
ParameterTypeRequiredDescription
idstringYesTransaction 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//note

Add or update a note on a transaction. Scope: write:transactions Path parameters
ParameterTypeRequiredDescription
idstringYesTransaction 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

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.