Skip to main content
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

All endpoints are prefixed with /v1. Include your Bearer token in the Authorization header:

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.
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[].accountName → balances[].type (on /v1/balances, for semantic clarity)
  • points_activity[].createdAt → points_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:
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

Balances & Finances

GET /v1/balances

Returns your account balances. Scope: read:balances Query parameters Response (full)
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 accountName → type per row.

GET /v1/lifetime-savings

Returns your lifetime accumulated savings from using Moneda — the total fees Moneda covered on your behalf (sponsored network and transfer fees) plus the discount your plan gives you versus the Standard plan, in your display currency. Scope: read:balances Response
components itemises the same total, in the same display currency: network fees Moneda paid on your behalf, on/off-ramp fees Moneda absorbed, and your plan’s discount on Moneda’s own fees versus Standard. Each is rounded independently, so the parts can differ from savings by a cent.

GET /v1/transactions

Returns your transaction history with powerful filtering. Scope: read:transactions Query parameters Response (full)
signer names the passkey that approved the payment: label is the nickname (falling back to the provider name) and kind is primary or backup. It is absent on rows that no user signed — anything created before signer provenance was recorded, and every server-initiated movement such as an incoming deposit, a scheduled transfer, or an on-ramp. Absent means “no user signature on file”, not “an unrecognised key”.
refundOf links a refund to the payment it reverses ({ id, transactionHash }, or null). refunds lists the completed refunds of a payment, oldest first, each with id, transactionHash, amount, currency and createdAt; it is empty when nothing was refunded.
Exchange transactions include additional fields:
Counterparty types 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.

GET /v1/transactions/{hash}

Single-resource lookup by transaction hash. Scope: read:transactions Path parameters Query parameters 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 Response

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

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

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

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

GET /v1/spending/daily

Daily spending totals for a stablecoin over a rolling window or absolute UTC range. Scope: read:spending Query parameters

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

GET /v1/exchange-rates

Exchange rate between two supported currencies. Scope: read:rates Query parameters Response

GET /v1/apy-rates

Current earnings APY rates. Scope: read:earnings Query parameters Response

GET /v1/dinari/capabilities

What the user may currently do with tokenized equities (dShares). Scope: read:account Response
brokerOrderFeeUsd is the broker’s per-order fee, separate from Moneda’s own trade fee. It is taken out of the amount on a buy and off the proceeds on a sale: a 4buyputs4 buy puts 3.80 into shares. It is 0 when the broker bills its network fees to Moneda instead. blockedReasons is ordered — render the first entry as the next thing the user should fix. It is empty if and only if canTrade is true.

GET /v1/dinari/us/requirements

What a US-jurisdiction user still owes before their brokerage KYC can be submitted. Scope: read:account Returns field names and counts only — never the user’s answers. A non-US user gets isUsCustomer: false and an empty, already-complete requirement set. Response
There is no write counterpart to this endpoint, by design. The US profile answers and the ten regulatory disclosures are completed by the user in the Moneda app: a consent recorded without the user having personally seen the verbatim document is not a consent, and the row carries an IP and timestamp that a broker-dealer treats as a signature. Use this endpoint to explain what is outstanding and send the user to onboardingPath.

GET /v1/dinari/market-hours

The US equity session clock. No scope required: this is the public exchange calendar and carries no account data. Response
windows are ISO instants for the trading day named by cycleEtDate. On a weekend or holiday that is the next trading day, so there is always a concrete cycle to show. Render the instants in the reader’s own timezone.

GET /v1/dinari/orders/free-trade-allowance

Commission-free stock trades left in the current window, without quoting an order. Scope: read:account Response
The window steps from the customer’s own subscription anniversary, clamped on short months. It does not reset on the 1st, so quote resetsAt rather than a day of the month. degraded: true means the count could not be read and the values are a fail-open default. Say so; do not present them as fact. upgrade is populated only once the allowance is spent, and names the cheapest higher plan whose allowance would still have covered this window.

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 Response

Account Info

GET /v1/wallet

Your smart wallet address on Base. Scope: read:account Query parameters Response (full)
Response (?view=lite) — ~76% smaller

GET /v1/virtual-accounts

IBAN and ACH virtual account details for receiving transfers. Scope: read:virtual_accounts Query parameters Response (full)
Status can be active, pending, or not_registered. Response (?view=lite) — ~78% smaller

GET /v1/virtual-accounts/limits

Remaining spending limit per direction on a rolling 52-week window, plus enhanced-due-diligence (EDD) status. Scope: read:virtual_accounts Crossing a limit does not reject a transfer — the banking partner pauses it and gives the user 24 hours to supply a Source-of-Wealth document before it is returned. Check this before proposing a large deposit or withdrawal. Response
rfis has the same shape as the items of GET /v1/virtual-accounts/information-requests. rfiRequired is true when one is waiting on the user’s answers. missingRequirements lists what Iron still needs while eddRequired is true (codes such as kyc_questionnaire, kyc_proof_of_residence, kyc_identity, kyc_selfie); the user finishes these in the app.

GET /v1/virtual-accounts/information-requests

Compliance information requests (RFIs) the banking partner (Iron/MoonPay) has sent the user. Scope: read:virtual_accounts Read-only by design: MoonPay requires the customer to answer personally, so there is no endpoint to submit answers or documents. Direct the user to Settings > Features and limits in the Moneda app. Response
status is waiting_on_response, needs_review or closed. openRound is null when nothing is waiting on the user. threshold: null means the customer has no enforced cap — not that the value is unknown. Limits are per direction and are not a combined budget: a customer may use their full limit on deposits and on withdrawals in the same window. Thresholds are personalised per customer (derived from declared income), so never hard-code one — always read it here.

GET /v1/virtual-accounts/settlement-estimate

The date a US bank (ACH) transfer settles. Scope: read:virtual_accounts Accounts for the 3:00 PM ET same-day cutoff, weekends, and US federal bank holidays — a transfer sent the afternoon before Thanksgiving does not arrive the next day. USD/ACH only; no equivalent published cutoff exists for EUR/SEPA. Query parameters Response
settlesOn is a calendar date in the bank’s timezone (ET). delayedByHoliday is true when a bank holiday, not merely a weekend, pushed the date out.

GET /v1/accounts

Your saved external wallets and bank accounts. Scope: read:external_accounts Query parameters Response (full)

GET /v1/contacts

Your saved contacts (Moneda users, external wallets, and bank accounts). Scope: read:contacts Query parameters Response

GET /v1/settings

Your app preferences. Scope: read:settings Response

API Keys

An API key is a long-lived credential for scripts, the CLI (--api-key), and integrations that can’t run an OAuth flow. Send it as X-API-Key. Two rules apply wherever a key is minted: it can never carry a scope the calling credential doesn’t already hold, and payment scopes are never available on an API key at all — moving money always goes through approval in the app.

GET /v1/api-keys

Your API keys. Scope: read:api_keys Returns { keys: [...] }. Each key carries id, name, prefix (the first 12 characters, enough to recognise it), scopes, lastUsedAt, expiresAt, revokedAt, createdAt, and isActive. The secret is stored hashed and is never returned here.

POST /v1/api-keys

Create an API key. Scope: write:api_keys Body Response The key field is the secret, and this response is the only place it ever appears — it can’t be retrieved again. A maximum of 10 active keys is allowed; revoke one to make room.

DELETE /v1/api-keys/{keyId}

Revoke an API key. Scope: write:api_keys The key is refused on its very next request. This can’t be undone — create a new key to restore access.

GET /v1/passkeys

Your authentication passkeys. Scope: read:passkeys Query Response (target=WALLET) kind tells a backup apart from the key you signed up with, and onchainStatus says whether it can actually sign yet: a pending backup is registered but its add-owner transaction has not confirmed.
Response (target=LOGIN) The credential id is masked and the public key is never returned.
This endpoint reports which keys exist, not which one signs next. The preferred signing key is chosen on the user’s own device and is never stored server-side, so no API can report it. Don’t infer it from kind or ordering.

PATCH /v1/passkeys/{id}/label

Rename one of your passkeys. Scope: write:profile Cosmetic only — it cannot add, remove, or weaken a credential. Use the id from GET /v1/passkeys. target is WALLET for a smart-account passkey or LOGIN for an email login passkey. A null or empty label clears the nickname so the derived provider name is shown again. Labels are capped at 40 characters and are not required to be unique. Body
Response
Returns 404 when the id does not belong to you.

GET /v1/recovery-contacts

Your recovery guardians. Scope: read:recovery Response

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 Response
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 Query parameters 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 Response
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 Response (no recovery in flight)
Response (recovery in flight)
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 Response
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

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 Response

GET /v1/sub-accounts/{id}

Single sub-account detail by id. Path parameters 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 Query parameters Balances are returned as raw bigint strings (6 decimals).

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

Approval requests on a joint account (M-of-N). Read-only: voting and executing require a passkey signature from the user’s device. Path parameters Query parameters A request past its deadline that no writer has swept yet reports isExpired: true while its status is still PENDING.

GET /v1/sub-accounts/proposals/{proposalId}

One approval request with its per-member approval list. Path parameters

POST /v1/sub-accounts/{id}/top-up

Propose moving money from your main account into this sub-account. Requires approval in the Moneda app. Scope: write:sub_accounts Request body
currency is one of USD, EUR, CHF, CAD, MXN. Response Same shape as POST /v1/payments, with paymentType set to sub_account_top_up. Poll GET /v1/payments/{id}/status for the result.

POST /v1/sub-accounts/{id}/withdraw

Propose moving money from this sub-account back to your main account (single-signature sub-accounts). Requires approval in the Moneda app. Scope: write:sub_accounts Same request body as top-up. paymentType is sub_account_withdraw.

POST /v1/sub-accounts/{id}/swap

Propose a swap inside this sub-account. One side must be a currency (USD, EUR, CHF, CAD, MXN); the other can be a crypto asset symbol such as cbBTC. The funds stay in the sub-account. Requires approval in the Moneda app. Scope: write:sub_accounts Request body
amount is in units of fromAsset. slippageBps is optional (0 to 200, default 50). paymentType is sub_account_swap.

POST /v1/sub-accounts/{id}/freeze

Propose freezing a sub-account you created. No request body. Unfreezing is only possible in the app. Scope: write:sub_accounts paymentType is sub_account_freeze.

POST /v1/sub-accounts/{id}/proposals

Open a SPEND or WITHDRAW proposal on a joint account that needs more than one approval. This only creates the proposal; co-owners approve and execute it in the Moneda app. Scope: write:sub_accounts Request body
For SPEND, name exactly one recipient: recipientUsername, accountId, or contactUsername / contactAccountId. Bank and cross-chain recipients are not supported. description is optional, up to 280 characters. paymentType is sub_account_proposal.

GET /v1/wealth

Aggregate wealth across the main account + every live vault. Scope: read:balances + read:sub_accounts. Query parameters 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
Response

DELETE /v1/sessions/{sessionId}

End a session. Path parameters Returns 204 No Content on success.

GET /v1/sessions/active

List active login sessions and connected agents (third-party services). Scope: read:sessions Response

DELETE /v1/sessions/active/{sessionId}

Terminate a specific active session. Scope: write:sessions Path parameters Response

POST /v1/sessions/revoke-all

Terminate all other active sessions, keeping the caller’s current session. Scope: write:sessions Response

Points & Referrals

GET /v1/points/balance

Your points balance. Scope: read:points Response

GET /v1/points/activity

Points earning history. Scope: read:points Query parameters Response

GET /v1/points/redemption-config

Config for redeeming points into AI credits: the points-per-$1 rate, the minimum redemption, the monthly cap, your current balance, and how much monthly allowance is left. Scope: read:points Response

POST /v1/points/redeem

Redeem Moneda Points for prepaid AI usage credits. Spends points and grants the matching AI budget atomically, enforcing the minimum, sufficient-balance, and monthly-cap checks. Scope: write:usage_credits Body Response

GET /v1/referrals/code

Your referral link and stats. Scope: read:referrals
The referral code is derived from your account (<username>-<id-prefix>). Set a username first — the code is unavailable until you do.
Response

GET /v1/referrals/referees

Friends you’ve referred. Scope: read:referrals Response

Knowledge Base

These endpoints are public — no authentication required.

GET /v1/knowledge/search

Search the FAQ knowledge base. Query parameters Response

GET /v1/knowledge/categories

List the published FAQ categories with each item’s id and question (no answers). Pass an id to GET /v1/knowledge/items/{id} for the answer. Response

GET /v1/knowledge/categories/

Browse a FAQ category by slug. Path parameters Response

GET /v1/knowledge/items/

Get a specific FAQ item. Path parameters Response

Users

GET /v1/users/search

Search Moneda users by username or display name. Scope: read:users Query parameters Response

Write Endpoints

PATCH /v1/profile

Update your display name. Scope: write:profile Request body
Response

PATCH /v1/transactions/{hash}/category

Assign a spending category to a transaction. Scope: write:transactions Path parameters Request body
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

PATCH /v1/transactions/{hash}/note

Add or update a note on a transaction. Scope: write:transactions Path parameters Request body
Response

POST /v1/transactions/batch/categories

Categorize up to 25 transactions at once. Scope: write:transactions Request body
Response

POST /v1/transactions/batch/notes

Add notes to up to 25 transactions at once. Scope: write:transactions Request body
Response

POST /v1/accounts/wallets

Save an external crypto wallet. Scope: write:external_accounts Request body
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
Or if confirmation is needed:

POST /v1/accounts/banks

Save a bank account (EU IBAN or US ACH). Scope: write:external_accounts Request body (EU)
Request body (US)
Response

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
Payment types Wallet payouts settle on Base. A payout to a contact wallet on another chain is refused with VALIDATION_ERROR; make it in the Moneda app. Payment source (optional) Response

POST /v1/payments/exchange

Exchange money between two of your own currency accounts. Requires approval in the Moneda app. Scope: write:payments fromCurrency is what leaves your account and toCurrency is what arrives. The destination is always your own account in toCurrency. Supported currencies: USD, EUR, CHF, CAD, MXN. Request body
Response Same shape as POST /v1/payments, with paymentType set to exchange. Poll GET /v1/payments/{id}/status for the result.

POST /v1/payments/crypto-trade

Buy or sell a crypto asset with your own balance. Requires approval in the Moneda app. Scope: write:trades Not for sending money (POST /v1/payments) or converting between currencies (POST /v1/payments/exchange). The server chooses the route. The user sees the live price, then approves and signs in the Moneda app. amount is what you spend: in stableCurrency on a BUY, in the asset on a SELL. Supported assets: cbBTC (Bitcoin), WETH (Ethereum). Supported currencies: USD, EUR, CAD, MXN. Request body
Response Same shape as POST /v1/payments, with paymentType set to crypto_swap. Poll GET /v1/payments/{id}/status for the result.

POST /v1/payments/token-buy

Buy a Base token that is not in the crypto-trade catalogue, by contract address, with your own balance. Requires approval in the Moneda app. Scope: write:trades amount is what you spend, in stableCurrency (USD, EUR, CHF or MXN). The token must be tradeable on Base. How much of it you get is priced when the user approves; they see it, then approve and sign in the Moneda app. Request body
Response Same shape as POST /v1/payments, with paymentType set to buy_token. Poll GET /v1/payments/{id}/status for the result.

POST /v1/payments/earnings-move

Deposit into or withdraw from an earnings vault with your own money. Requires approval in the Moneda app. Scope: write:payments vaultProvider is MORPHO (Earnings: USD, EUR, MXN) or YO (Smart Earnings: USD, EUR). A DEPOSIT comes from the account in currency; a WITHDRAW goes back to it. Request body
Response Same shape as POST /v1/payments, with paymentType set to earnings_move. Poll GET /v1/payments/{id}/status for the result.

GET /v1/payments//status

Check the status of a payment request. Scope: write:payments Path parameters Response
Payment statuses: PENDING_APPROVAL, APPROVED, COMPLETED, FAILED, EXPIRED, REJECTED

POST /v1/x402-payments

Pay an x402 (HTTP 402 Payment Required) invoice to a wallet address. Requires approval in the Moneda app, then settles on-chain to the resource’s payTo address. Scope: write:payments
Like other payments, x402 invoices are not executed immediately — the user must approve in the Moneda mobile app within 5 minutes. Poll GET /v1/payments/{id}/status for the result, then retry the original HTTP request once it is COMPLETED.
Request body
Response

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

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

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 (UPLOADING / PROCESSING / VALID / INVALID / DELETED, optional), limit (default 20, max 100), offset (optional). Paging is offset-based here, not cursor-based.

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. Scope: read:receipts Query parameters: query (required), category (optional), startDate / endDate (ISO dates, optional), limit and offset (optional).

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}/reparse

Re-run OCR + LLM extraction on a receipt already stored in S3 — no re-upload needed. Use after a failed parse, a wrong extraction, or an extraction-model upgrade. Synchronous: the response is the post-extraction snapshot (status, confidence, document type, line-item count). Refuses deleted receipts and receipts with no stored file. 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}/partial-payment

Mark a receipt as a partial payment (a deposit or instalment against a larger invoice), so a total that differs from the transaction amount stops being flagged. Replaces the amount_mismatch review reason with partial_payment; amounts are unchanged. Scope: write:receipts

PATCH /v1/receipts/{receiptId}/amounts

Correct a receipt’s top-line amounts: totalAmountValue, subtotalValue, taxAmountValue, taxRateValue (a percentage, 19 for 19%) and tipAmountValue. Omit a field to leave it unchanged, send null to clear it; at least one field is required and values must be non-negative. Returns the amounts after the update. A later reparse re-extracts and overwrites them. 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 Query parameters: status (optional), limit (optional), cursor (optional).

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 Query parameters: transactionHash (optional), limit and offset (optional).

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 Query parameters: transactionHash (optional), limit and offset (optional).

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

Scheduled (Recurring) Transactions

Read + lifecycle (pause/resume) endpoints for scheduled transactions, plus proposing one. 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. A proposal is a draft the user activates in the app.

GET /v1/scheduled-transactions

List the user’s scheduled (recurring) transactions. Scope: read:transactions Query parameters: status (optional).

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 Query parameters: limit (optional).

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

List past executions for one schedule. Scope: read:transactions Query parameters: limit (optional), cursor (optional).

POST /v1/scheduled-transactions/propose

Propose a recurring payment. Creates a draft (PENDING_ACTIVATION) and notifies the user, who reviews it and activates it by signing with their passkey in the Moneda app. Nothing is sent until then, and a draft that is not approved within 48 hours expires. Not for one-off payments (use POST /v1/payments). Scope: write:payments paymentType is transfer_moneda (pass recipientUsername) or transfer_contact_wallet (pass contactUsername or contactAccountId; Base wallets only). frequency is DAILY, WEEKLY, BIWEEKLY, MONTHLY or QUARTERLY. Dates are YYYY-MM-DD in the user’s timezone; endDate is optional. Supported currencies: USD, EUR, CHF, CAD. Request body
Response: scheduleId, status (PENDING_ACTIVATION), amount, currency, frequency, startDate, endDate, recipient, expiresAt, notificationSent. Track it with GET /v1/scheduled-transactions/{id}: the status becomes ACTIVE once the user signs.

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. Returns the new nextExecutionAt, which is always in the future — an execution window that elapsed while the schedule was paused is skipped, never run retroactively. Fails with 422 if the schedule’s end date has already passed. Scope: write:transactions

Crypto Conditional Orders

Read and cancel crypto limit, stop-loss, take-profit and trailing-stop orders. Creating an order is NOT exposed via REST: it needs the user’s passkey to sign the Smart Sessions install and the CoW approval, which is mobile-app only.

GET /v1/conditional-orders

List the user’s crypto conditional orders, newest first. Scope: read:transactions Response: { "orders": [...] }. Each order has id, assetSymbol, side, type, status (PENDING_ACTIVATION, ARMED, PUBLISHED, FILLED, CANCELLED, EXPIRED), triggerPrice, trailPercent, sellAmount and minBuyAmount (raw token units), expiresAt, skipReason, createdAt, publishedAt, filledAt.

GET /v1/conditional-orders/{id}

Get one crypto conditional order by id. Scope: read:transactions

POST /v1/conditional-orders/{id}/cancel

Cancel an open order. A PUBLISHED order is live in the CoW order book, so it is withdrawn on-chain first and only then marked CANCELLED; if the withdrawal fails the order stays live and the call returns 500 (retry). Returns 409 for a filled, cancelled or expired order. Scope: write:trades

Agent Sessions

Read + lifecycle (pause/resume/revoke) endpoints for scoped autonomous agent sessions — a bounded mandate an agent can spend against without per-payment approval. Create / execute and the passkey-bound install are served separately. Both scopes are opt-in (not granted by default).

POST /v1/agent-sessions

Create a scoped autonomous payment session. Does NOT activate — returns a counterfactual child Safe address and an activation link the user opens in the Moneda app to install the on-chain policy with a passkey. Body: recipient, currency (USD/EUR), maxAmountPerExecution, maxAmountPerPeriod (user-facing units), periodSeconds, optional expiresAt, fundingAmount. Scope: write:agent_sessions

GET /v1/agent-sessions

List the user’s agent sessions, newest first, with status, spending policy, and remaining period cap. Optional status query filter. Scope: read:agent_sessions

POST /v1/agent-sessions/{id}/payments

Execute a payment within an ACTIVE session’s on-chain policy — no per-payment approval. Body: recipient, amount (user-facing units), currency, optional idempotencyKey for safe retries. Scope: write:agent_sessions

GET /v1/agent-sessions/{id}

Get one agent session by id, including its 5 most recent executions. Scope: read:agent_sessions

POST /v1/agent-sessions/{id}/pause

Pause an active session (backend-only flip; halts execution, reversible via /resume). Scope: write:agent_sessions

POST /v1/agent-sessions/{id}/resume

Resume a manually-paused session. Auto-paused sessions need their underlying condition cleared first. Scope: write:agent_sessions

POST /v1/agent-sessions/{id}/revoke

Revoke a session — immediately and permanently halts all execution. The on-chain uninstall is completed separately in the Moneda app. Scope: write:agent_sessions

Usage Credits

POST /v1/usage-credits/purchase

Redeem a completed top-up payment to Moneda for prepaid AI usage credits. The amount is read from the confirmed on-chain payment (never from input), converted to USD budget, and granted exactly once — re-calling with the same hash is safe (idempotent). The payment is already finished, so there is nothing more to approve. Body: transactionHash. Scope: write:usage_credits Response

PATCH /v1/usage-credits/monthly-limit

Set or clear the monthly spend limit on AI usage credits — the ceiling on how much of the prepaid balance may be consumed per calendar month (resets the 1st, UTC). Body: monthlyLimitUsd (number 0–100000, or null for unlimited). Scope: write:usage_credits Response

Subscriptions

Read + invoice-generation surface for subscription billing. The write surface (subscribe / cancel / change plan) stays in tRPC for now — it needs on-chain Smart Session ceremonies that don’t fit a synchronous REST request. Gated by the ENABLE_BALANCE_PAY_SUBSCRIPTIONS feature flag in the services layer (a disabled flag surfaces as 403).

GET /v1/subscriptions/charges

Cursor-paginated list of the authenticated user’s subscription charges (SUBSCRIPTION_CHARGE rows), newest first. Scope: read:subscriptions Query parameters: limit (optional), cursor (optional).

POST /v1/subscriptions/charges/{chargeId}/invoice

Idempotently generate (and upload) the ZUGFeRD plan-invoice PDF for a charge and return a 1-hour presigned downloadUrl. Re-calling for the same charge reuses the existing invoice. Returns 404 if the charge doesn’t exist or isn’t owned by the caller. Scope: read:subscriptions

Platform (Management API)

The Platform Management API (/v1/platform/*) lets developers manage their organization, workspaces, members, invites, and platform API keys. These endpoints are authorized by the calling user’s organization role (owner / admin / developer / billing / compliance / member), not by MCP scopes. See the Moneda Platform for the full model.

POST /v1/platform/organizations

Create an organization. The caller is seated as its OWNER. Body: name, slug.

GET /v1/platform/organizations

List the organizations the caller is an active member of, with their role in each.

POST /v1/platform/organizations/{orgId}/workspaces

Create a workspace under an organization. Requires a key-manager role (owner / admin / developer). Body: name, slug.

GET /v1/platform/organizations/{orgId}/workspaces

List the workspaces in an organization.

POST /v1/platform/organizations/{orgId}/members

Add (or re-activate) a member by user id. Requires a member-manager role (owner / admin); only an owner may grant or modify the OWNER role. Body: userId, role.

GET /v1/platform/organizations/{orgId}/members

List the members of an organization with their roles.

POST /v1/platform/organizations/{orgId}/invites

Invite a member by email. Requires a member-manager role. Returns the raw invite token once. Body: email, role.

GET /v1/platform/organizations/{orgId}/invites

List an organization’s invites with their status (pending / accepted / expired). Never exposes the token hash.

POST /v1/platform/invites/accept

Accept an invite. Body: the raw invite token. Seats the caller as an organization member.

POST /v1/platform/workspaces/{workspaceId}/keys

Issue a workspace-scoped platform API key. Requires a key-manager role. The raw secret is returned exactly once. Body: name, scopes (money-movement scopes are not available on platform keys), optional mode. mode: "live" (default) mints an mnd_live_… key billed against org credits; mode: "test" mints an mnd_test_… sandbox key — it never decrements credits, never calls paid upstreams, and uses separate rate buckets. (Sandbox tool calls are served as deterministic fixtures; tool execution over platform keys — live and sandbox — ships with the tool API and is not yet available.) Live and sandbox keys have independent per-workspace caps. The response sandbox flag reflects the key’s mode.

GET /v1/platform/workspaces/{workspaceId}/keys

List a workspace’s platform API keys (metadata only — never the secret).

POST /v1/platform/keys/{keyId}/rotate

Rotate a platform key: mints a replacement and puts the old key on a grace window so it can be swapped with zero downtime. Requires a key-manager role.

DELETE /v1/platform/keys/{keyId}

Revoke a platform key immediately. Requires a key-manager or compliance role.

POST /v1/platform/organizations/{orgId}/limits

Create or reconfigure a spend/rate limit. Requires a member-manager role (owner / admin). Body: scope (ORG/WORKSPACE/KEY), targetId, limitType (SPEND_HARD/SPEND_SOFT/RATE_RPM/RATE_TPM/RATE_RPD), value, optional windowSeconds (rolling window; defaults to the calendar month for spend), and optional notifyThresholdPct (1–99). Upserts on (scope, targetId, limitType).

GET /v1/platform/organizations/{orgId}/limits

List every limit configured for the org (its own, plus workspace- and key-scoped limits). Any member may read.

DELETE /v1/platform/organizations/{orgId}/limits/{limitId}

Remove a limit. Requires a member-manager role.

POST /v1/platform/workspaces/{workspaceId}/webhooks

Register a webhook endpoint for a workspace. Requires a member-manager role. Body: url (https only, and must resolve to a public address — loopback/private/link-local targets are rejected at registration and re-checked at every delivery), optional description, and enabledEvents (one or more of the event catalog below). The signingSecret is returned exactly once — store it to verify signatures.

GET /v1/platform/workspaces/{workspaceId}/webhooks

List a workspace’s webhook endpoints (metadata only — never the signing secret).

PATCH /v1/platform/webhooks/{webhookId}

Update an endpoint’s url, description, enabledEvents, or status (ENABLED/DISABLED). Requires a member-manager role.

DELETE /v1/platform/webhooks/{webhookId}

Delete a webhook endpoint (cascades its pending deliveries). Requires a member-manager role. Event catalog: usage.threshold_reached, usage.limit_exceeded, usage.rate_limited, credit.low, credit.exhausted, key.created, key.revoked. Signature verification. Each delivery is a POST with headers X-Moneda-Event, X-Moneda-Delivery, X-Moneda-Timestamp, and X-Moneda-Signature. Verify by computing HMAC-SHA256(signingSecret, "v0:" + timestamp + ":" + rawBody) (hex) and comparing against X-Moneda-Signature; reject timestamps outside a few minutes to prevent replay. Delivery is at-least-once. A retry (or an overlapping dispatch of a still-in-flight delivery) can send the same event more than once. Deduplicate on X-Moneda-Delivery (unique per delivery attempt) or the event’s id, and make your handler idempotent. Return a 2xx promptly; any other status is retried with exponential backoff up to 6 attempts.

GET /v1/platform/organizations/{orgId}/audit-log

Read the organization audit log (policy-changing actions: key/limit/webhook/member changes), newest first. Restricted to owner / admin / compliance roles. Query: optional action filter, before (opaque cursor — pass a prior response’s nextBefore), limit (default 50, max 200). Response includes nextBefore for keyset pagination.

GET /v1/platform/organizations/{orgId}/usage

Read the organization’s metered request log (one row per billed call), newest first. Any member may read. Query: optional workspaceId, apiKeyId, tool filters, before (opaque cursor — pass a prior response’s nextBefore), limit (default 50, max 200). Money fields (costUsd, upstreamCostUsd) are strings; response includes nextBefore.

GET /v1/platform/pricing

The machine-readable rate card: one row per tool with unitPriceUsd, markupMultiplier, and the effective pricePerUnitUsd (unit × markup), plus unit (PER_CALL/PER_1K_TOKENS/PER_GB_DAY), category, and scope. Money fields are decimal strings. This is the global default table.

GET /v1/platform/organizations/{orgId}/contract

Read the org’s enterprise contract (committed spend, term, SLA tier, dedicated-capacity flag, legal-doc references) plus its billingMode and dataResidency. Returns null if the org has no contract. Any member may read.

PUT /v1/platform/organizations/{orgId}/contract

Create or update the enterprise contract terms. Requires a member-manager role. Body (all optional): status (DRAFT/ACTIVE/EXPIRED/TERMINATED), committedAmountUsd, termStart, termEnd, autoRenew, slaTier, dedicatedUpstreamCapacity, legalDocRefs ([{ kind, url, version?, signedAt? }] — pointers to MSA/DPA/SLA, never the documents), dataResidency. Provisioning is sales-led; in production this is a Moneda-admin action.

POST /v1/platform/organizations/{orgId}/billing-mode

Set the org’s billing mode. Requires a member-manager role. Body: billingMode (PREPAID or INVOICED). INVOICED — where usage accrues to a monthly invoice against the contract instead of decrementing prepaid credits — requires an ACTIVE contract.

Error Responses

All errors follow a consistent format:
Error codes

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.