> ## 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.

# API Endpoints

> Complete reference of all Moneda REST API endpoints with parameters, responses, and examples

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.

<Info>
  Write endpoints that involve money — like initiating payments — always require your confirmation in the Moneda app before anything happens.
</Info>

## Base URL

```
https://api.moneda.com/v1
```

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

```bash theme={null}
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.

| `view` | Default | Use case                                                                                                                                               |
| ------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `full` | ✅       | Back-compat — every field the service exposes. Existing integrations keep working unchanged.                                                           |
| `lite` | —       | Smaller response with optional metadata, coaching fields, derived counts, and empty/null optionals dropped. Designed for LLM agents and CLI rendering. |

<Tip>
  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.
</Tip>

`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:**

```bash theme={null}
# 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**

```json theme={null}
{
  "status": "ok",
  "uptime": 12345,
  "version": "1.0.0"
}
```

***

## Balances & Finances

### GET /v1/balances

Returns your account balances. Scope: `read:balances`

**Query parameters**

| Parameter  | Type                    | Required | Description                                                                                   |
| ---------- | ----------------------- | -------- | --------------------------------------------------------------------------------------------- |
| `currency` | `USD` \| `EUR` \| `CHF` | No       | Filter by currency                                                                            |
| `view`     | `full` \| `lite`        | No       | Response shape selector. Default `full`. See [above](#response-shape-view-full-vs-view-lite). |

**Response (full)**

```json theme={null}
{
  "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 `accountName` → `type` per row.

```json theme={null}
{
  "balances": [
    { "currency": "USD", "type": "USD Account", "balance": "1250.50" },
    { "currency": "EUR", "type": "EUR Account", "balance": "890.25" }
  ],
  "stale": "2 minutes ago"
}
```

***

### 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**

```json theme={null}
{
  "savings": 1234.56,
  "currencySymbol": "$"
}
```

***

### GET /v1/transactions

Returns your transaction history with powerful filtering. Scope: `read:transactions`

**Query parameters**

| Parameter         | Type                                                  | Required | Description                                                                                      |
| ----------------- | ----------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------ |
| `currency`        | `USD` \| `EUR` \| `CHF`                               | No       | Filter by currency                                                                               |
| `type`            | string                                                | No       | Filter by exact transaction type (e.g. `TRANSFER`, `SWAP`)                                       |
| `typeGroup`       | string                                                | No       | Filter by group: `transfer`, `exchange`, `deposit`, `withdrawal`, `earnings`, `topup`, `offramp` |
| `category`        | string                                                | No       | Filter by spending category (e.g. `GROCERIES`, `TRAVEL`)                                         |
| `status`          | `COMPLETED` \| `PENDING` \| `FAILED` \| `all`         | No       | Default: `COMPLETED` + `COMPLETED_DELAYED`. `all` to include every status.                       |
| `direction`       | `incoming` \| `outgoing` \| `exchange`                | No       | Filter by transaction direction relative to the user                                             |
| `counterparty`    | string                                                | No       | Search by counterparty username, wallet name, address, or external provider                      |
| `transactionHash` | string                                                | No       | Exact hash lookup (with or without `0x` prefix, case-insensitive). Returns at most one row.      |
| `search`          | string                                                | No       | Free-text substring search over note + reference fields                                          |
| `startDate`       | ISO 8601                                              | No       | Filter from date                                                                                 |
| `endDate`         | ISO 8601                                              | No       | Filter until date                                                                                |
| `year`            | number                                                | No       | Filter by year (overrides startDate/endDate)                                                     |
| `minAmount`       | number                                                | No       | Minimum amount filter                                                                            |
| `maxAmount`       | number                                                | No       | Maximum amount filter                                                                            |
| `hasNote`         | `true` \| `false`                                     | No       | Filter by whether transactions have a note                                                       |
| `hasCategory`     | `true` \| `false`                                     | No       | Filter by whether transactions have a category                                                   |
| `sort`            | `newest` \| `oldest` \| `amount_desc` \| `amount_asc` | No       | Default: `newest`                                                                                |
| `limit`           | number                                                | No       | Max 50, default 5                                                                                |
| `offset`          | number                                                | No       | Pagination offset                                                                                |
| `view`            | `full` \| `lite`                                      | No       | Response shape selector. Default `full`. See [above](#response-shape-view-full-vs-view-lite).    |

**Response (full)**

```json theme={null}
{
  "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:

```json theme={null}
{
  "direction": "exchange",
  "amount": "0",
  "exchanged": "100.00",
  "exchangedCurrency": "USD",
  "received": "92.15",
  "receivedCurrency": "EUR"
}
```

**Counterparty types**

| Type       | Fields                               | Description                               |
| ---------- | ------------------------------------ | ----------------------------------------- |
| `self`     | —                                    | Self-transfer (swap, deposit, withdrawal) |
| `user`     | `username`                           | Moneda user                               |
| `provider` | `name`                               | Service provider (Coinbase, etc.)         |
| `bank`     | `name`, `iban`, `account`, `routing` | Bank transfer                             |
| `wallet`   | `name`, `address`                    | External wallet                           |
| `unknown`  | `address`                            | Unknown 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.

```json theme={null}
{
  "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**

| Parameter | Type   | Description                                                      |
| --------- | ------ | ---------------------------------------------------------------- |
| `hash`    | string | Transaction hash (with or without `0x` prefix, case-insensitive) |

**Query parameters**

| Parameter | Type             | Required | Description                                                                                                          |
| --------- | ---------------- | -------- | -------------------------------------------------------------------------------------------------------------------- |
| `view`    | `full` \| `lite` | No       | Default `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**

| Parameter  | Type                   | Required | Description        |
| ---------- | ---------------------- | -------- | ------------------ |
| `period`   | `7d` \| `30d` \| `90d` | No       | Default: `30d`     |
| `currency` | `USD` \| `EUR`         | No       | Filter by currency |

**Response**

```json theme={null}
{
  "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**

| Parameter  | Type                   | Required | Description        |
| ---------- | ---------------------- | -------- | ------------------ |
| `period`   | `7d` \| `30d` \| `90d` | No       | Default: `30d`     |
| `currency` | `USD` \| `EUR`         | No       | Filter 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**

| Parameter   | Type                   | Required | Description                                                  |
| ----------- | ---------------------- | -------- | ------------------------------------------------------------ |
| `accountId` | string                 | Yes      | Recipient account id (from `/v1/contacts` or `/v1/accounts`) |
| `period`    | `7d` \| `30d` \| `90d` | No       | Default: `30d`                                               |
| `currency`  | `USD` \| `EUR`         | No       | Filter 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**

| Parameter  | Type                   | Required | Description        |
| ---------- | ---------------------- | -------- | ------------------ |
| `period`   | `7d` \| `30d` \| `90d` | No       | Default: `30d`     |
| `currency` | `USD` \| `EUR`         | No       | Filter 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**

| Parameter      | Type           | Required | Description                          |
| -------------- | -------------- | -------- | ------------------------------------ |
| `currentFrom`  | ISO date       | Yes      | Start of the current period (UTC)    |
| `currentTo`    | ISO date       | Yes      | End of the current period (UTC)      |
| `previousFrom` | ISO date       | Yes      | Start of the comparison period (UTC) |
| `previousTo`   | ISO date       | Yes      | End of the comparison period (UTC)   |
| `currency`     | `USD` \| `EUR` | No       | Filter 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**

| Parameter  | Type                                  | Required | Description                                                   |
| ---------- | ------------------------------------- | -------- | ------------------------------------------------------------- |
| `currency` | `USDC` \| `EURC` \| `CHFAU` \| `CADD` | Yes      | Token to chart                                                |
| `window`   | `1d` \| `1w` \| `1m` \| `1y`          | No       | Rolling window from now (mutually exclusive with `from`/`to`) |
| `from`     | ISO date                              | No       | Absolute window start (UTC)                                   |
| `to`       | ISO date                              | No       | Absolute 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**

| Parameter   | Type                   | Required | Description                              |
| ----------- | ---------------------- | -------- | ---------------------------------------- |
| `period`    | `7d` \| `30d` \| `90d` | No       | Default: `30d`                           |
| `currency`  | `USD` \| `EUR`         | No       | Filter by currency                       |
| `category`  | string                 | No       | Drill into one category bucket           |
| `accountId` | string                 | No       | Drill into one merchant/recipient bucket |

***

### GET /v1/exchange-rates

Current USD/EUR exchange rate. Scope: `read:rates`

**Query parameters**

| Parameter | Type           | Required | Description    |
| --------- | -------------- | -------- | -------------- |
| `from`    | `USD` \| `EUR` | No       | Default: `USD` |
| `to`      | `USD` \| `EUR` | No       | Default: `EUR` |

**Response**

```json theme={null}
{
  "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**

| Parameter  | Type           | Required | Description        |
| ---------- | -------------- | -------- | ------------------ |
| `currency` | `USD` \| `EUR` | No       | Filter by currency |

**Response**

```json theme={null}
[
  {
    "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**

| Parameter   | Type                         | Required | Description      |
| ----------- | ---------------------------- | -------- | ---------------- |
| `accountId` | string                       | Yes      | Vault account id |
| `window`    | `1w` \| `1m` \| `3m` \| `1y` | No       | Default: `1m`    |

**Response**

```json theme={null}
{
  "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**

| Parameter | Type             | Required | Description                                                                  |
| --------- | ---------------- | -------- | ---------------------------------------------------------------------------- |
| `view`    | `full` \| `lite` | No       | Default `full`. `lite` drops `network`, `supportedTokens`, `warning`, `tip`. |

**Response (full)**

```json theme={null}
{
  "smartAccountAddress": "0x1234...abcd",
  "network": "Base",
  "supportedTokens": ["USDC", "EURC", "CHFAU", "CADD"],
  "username": "@john",
  "warning": "Never send tokens on other networks to this address.",
  "tip": "This is your Base smart account address. Only send USDC, EURC, CHFAU, or CADD on the Base network."
}
```

**Response (`?view=lite`) — \~76% smaller**

```json theme={null}
{ "smartAccountAddress": "0x1234...abcd", "username": "@john" }
```

***

### GET /v1/virtual-accounts

IBAN and ACH virtual account details for receiving transfers. Scope: `read:virtual_accounts`

**Query parameters**

| Parameter | Type             | Required | Description                                                                                                                                             |
| --------- | ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `view`    | `full` \| `lite` | No       | Default `full`. `lite` drops `capabilityInfo`, `minimumTransfer`, `firstPartyPayments`, `thirdPartyPayments`, and the per-account `type` discriminator. |

**Response (full)**

```json theme={null}
{
  "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**

```json theme={null}
{
  "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**

| Parameter | Type             | Required | Description                                                                                   |
| --------- | ---------------- | -------- | --------------------------------------------------------------------------------------------- |
| `view`    | `full` \| `lite` | No       | Default `full`. `lite` drops `walletCount`, `bankCount`, wallet `address`, wallet `provider`. |

**Response (full)**

```json theme={null}
{
  "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**

| Parameter | Type   | Required | Description                          |
| --------- | ------ | -------- | ------------------------------------ |
| `search`  | string | No       | Search by name, username, or address |
| `limit`   | number | No       | Max results                          |
| `offset`  | number | No       | Pagination offset                    |

**Response**

```json theme={null}
{
  "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**

```json theme={null}
{
  "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**

```json theme={null}
{
  "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**

```json theme={null}
{
  "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**

| Parameter   | Type             | Required | Description                                                     |
| ----------- | ---------------- | -------- | --------------------------------------------------------------- |
| `accountId` | UUID             | No       | Defaults to the user's main LOCAL account.                      |
| `view`      | `full` \| `lite` | No       | Default `full`. `lite` drops `notes` and irrelevant timestamps. |

**Response**

```json theme={null}
{
  "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**

| Parameter | Type | Description                                 |
| --------- | ---- | ------------------------------------------- |
| `id`      | UUID | Recovery email id (`zkRecoveryGuardian.id`) |

**Query parameters**

| Parameter | Type             | Required | Description     |
| --------- | ---------------- | -------- | --------------- |
| `view`    | `full` \| `lite` | No       | Default `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**

| Parameter   | Type             | Required | Description                                                                                               |
| ----------- | ---------------- | -------- | --------------------------------------------------------------------------------------------------------- |
| `accountId` | UUID             | No       | Defaults to the user's main LOCAL account.                                                                |
| `view`      | `full` \| `lite` | No       | Default `full`. `lite` adds `fullySet: boolean` and drops `accountAddress` + per-row `id` / `acceptedAt`. |

**Response**

```json theme={null}
{
  "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**

| Parameter   | Type             | Required | Description                                |
| ----------- | ---------------- | -------- | ------------------------------------------ |
| `accountId` | UUID             | No       | Defaults to the user's main LOCAL account. |
| `view`      | `full` \| `lite` | No       | Default `full`.                            |

**Response (no recovery in flight)**

```json theme={null}
{ "hasActiveRecovery": false }
```

**Response (recovery in flight)**

```json theme={null}
{
  "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**

| Parameter | Type              | Required | Description                                            |
| --------- | ----------------- | -------- | ------------------------------------------------------ |
| `cursor`  | UUID              | No       | Id of the last notification from the previous page.    |
| `limit`   | number            | No       | Page size, max 50, default 20.                         |
| `filter`  | `all` \| `unread` | No       | Default `all`. Pass `unread` to scope to unread items. |

**Response**

```json theme={null}
{
  "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**

```json theme={null}
{ "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**

| Parameter       | Type             | Required | Description                                              |
| --------------- | ---------------- | -------- | -------------------------------------------------------- |
| `includeClosed` | boolean          | No       | Default `false`. Pass `true` to include archived vaults. |
| `view`          | `full` \| `lite` | No       | Default `full`.                                          |

**Response**

```json theme={null}
{
  "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**

| Parameter | Type | Description    |
| --------- | ---- | -------------- |
| `id`      | UUID | Sub-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**

| Parameter | Type | Description    |
| --------- | ---- | -------------- |
| `id`      | UUID | Sub-account id |

**Query parameters**

| Parameter  | Type                                  | Required | Description                                                |
| ---------- | ------------------------------------- | -------- | ---------------------------------------------------------- |
| `currency` | `USDC` \| `EURC` \| `CHFAU` \| `CADD` | No       | Filter to a single token. Omit for every enabled currency. |
| `view`     | `full` \| `lite`                      | No       | Default `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**

| Parameter | Type             | Required | Description     |
| --------- | ---------------- | -------- | --------------- |
| `view`    | `full` \| `lite` | No       | Default `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**

```json theme={null}
{
  "deviceName": "moneda-cli",
  "platform": "darwin"
}
```

**Response**

```json theme={null}
{
  "sessionId": "ses_abc123",
  "createdAt": "2026-04-01T00:00:00.000Z"
}
```

***

### DELETE /v1/sessions/\{sessionId}

End a session.

**Path parameters**

| Parameter   | Type | Description                         |
| ----------- | ---- | ----------------------------------- |
| `sessionId` | UUID | Session id from the create response |

Returns `204 No Content` on success.

***

## Points & Referrals

### GET /v1/points/balance

Your points balance. Scope: `read:points`

**Response**

```json theme={null}
{
  "totalPoints": 1500
}
```

***

### GET /v1/points/activity

Points earning history. Scope: `read:points`

**Query parameters**

| Parameter | Type   | Required | Description       |
| --------- | ------ | -------- | ----------------- |
| `limit`   | number | No       | Max results       |
| `offset`  | number | No       | Pagination offset |

**Response**

```json theme={null}
{
  "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`

<Info>
  The referral code is derived from your account (`<username>-<id-prefix>`). Set a username first — the code is unavailable until you do.
</Info>

**Response**

```json theme={null}
{
  "code": "alice-fc17d96c",
  "link": "https://moneda.com/?grsf=alice-fc17d96c",
  "referralCount": 5
}
```

***

### GET /v1/referrals/referees

Friends you've referred. Scope: `read:referrals`

**Response**

```json theme={null}
{
  "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**

| Parameter  | Type   | Required | Description             |
| ---------- | ------ | -------- | ----------------------- |
| `query`    | string | No       | Search query            |
| `category` | string | No       | Filter by category slug |
| `limit`    | number | No       | Max results             |

**Response**

```json theme={null}
{
  "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/{slug}

Browse a FAQ category by slug.

**Path parameters**

| Parameter | Type   | Required | Description                                  |
| --------- | ------ | -------- | -------------------------------------------- |
| `slug`    | string | Yes      | Category slug (e.g. `transfers`, `security`) |

**Response**

```json theme={null}
{
  "category": "Transfers",
  "slug": "transfers",
  "itemCount": 8,
  "items": [
    {
      "id": "faq_001",
      "question": "Are there fees for transfers?",
      "answer": "...",
      "category": "Transfers"
    }
  ]
}
```

***

### GET /v1/knowledge/items/{id}

Get a specific FAQ item.

**Path parameters**

| Parameter | Type   | Required | Description |
| --------- | ------ | -------- | ----------- |
| `id`      | string | Yes      | FAQ item ID |

**Response**

```json theme={null}
{
  "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**

| Parameter | Type   | Required | Description  |
| --------- | ------ | -------- | ------------ |
| `query`   | string | Yes      | Search query |

**Response**

```json theme={null}
{
  "users": [
    {
      "username": "@alice",
      "displayName": "Alice Smith"
    }
  ],
  "count": 1
}
```

***

## Write Endpoints

### PATCH /v1/profile

Update your display name. Scope: `write:profile`

**Request body**

```json theme={null}
{
  "displayName": "John Doe"
}
```

**Response**

```json theme={null}
{
  "success": true
}
```

***

### PATCH /v1/transactions/\{hash}/category

Assign a spending category to a transaction. Scope: `write:transactions`

**Path parameters**

| Parameter | Type   | Required | Description      |
| --------- | ------ | -------- | ---------------- |
| `hash`    | string | Yes      | Transaction hash |

**Request body**

```json theme={null}
{
  "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**

```json theme={null}
{
  "success": true
}
```

***

### PATCH /v1/transactions/\{hash}/note

Add or update a note on a transaction. Scope: `write:transactions`

**Path parameters**

| Parameter | Type   | Required | Description      |
| --------- | ------ | -------- | ---------------- |
| `hash`    | string | Yes      | Transaction hash |

**Request body**

```json theme={null}
{
  "note": "Dinner at Luigi's"
}
```

**Response**

```json theme={null}
{
  "success": true
}
```

***

### POST /v1/transactions/batch/categories

Categorize up to 25 transactions at once. Scope: `write:transactions`

**Request body**

```json theme={null}
{
  "updates": [
    { "transactionHash": "0xabc...123", "category": "GROCERIES" },
    { "transactionHash": "0xdef...456", "category": "TRAVEL" }
  ]
}
```

**Response**

```json theme={null}
{
  "succeeded": 2,
  "failed": 0
}
```

***

### POST /v1/transactions/batch/notes

Add notes to up to 25 transactions at once. Scope: `write:transactions`

**Request body**

```json theme={null}
{
  "updates": [
    { "transactionHash": "0xabc...123", "note": "Weekly groceries" },
    { "transactionHash": "0xdef...456", "note": "Flight to Paris" }
  ]
}
```

**Response**

```json theme={null}
{
  "succeeded": 2,
  "failed": 0
}
```

***

### POST /v1/accounts/wallets

Save an external crypto wallet. Scope: `write:external_accounts`

**Request body**

```json theme={null}
{
  "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**

```json theme={null}
{
  "success": true,
  "wallet": {
    "id": "acc_123",
    "name": "MetaMask",
    "address": "0x1234...5678",
    "network": "Base",
    "supportedTokens": ["USDC", "EURC"]
  }
}
```

Or if confirmation is needed:

```json theme={null}
{
  "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)**

```json theme={null}
{
  "firstName": "John",
  "lastName": "Doe",
  "country": "DE",
  "iban": "DE89370400440532013000",
  "bic": "COBADEFFXXX",
  "confirmed": true
}
```

**Request body (US)**

```json theme={null}
{
  "firstName": "John",
  "lastName": "Doe",
  "country": "US",
  "accountNumber": "1234567890",
  "routingNumber": "021000021",
  "accountType": "CHECKING",
  "confirmed": true
}
```

**Response**

```json theme={null}
{
  "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`

<Warning>
  Payments are not executed immediately. After calling this endpoint, the user must approve the payment in the Moneda mobile app within 5 minutes.
</Warning>

**Request body**

```json theme={null}
{
  "paymentType": "transfer_moneda",
  "amount": "50.00",
  "currency": "USD",
  "recipientUsername": "@alice",
  "reference": "Dinner split"
}
```

**Payment types**

| Type                      | Required fields                       | Description                      |
| ------------------------- | ------------------------------------- | -------------------------------- |
| `transfer_moneda`         | `recipientUsername`                   | Send to a Moneda user            |
| `transfer_account`        | `accountId`                           | Send to a saved external account |
| `transfer_contact_bank`   | `contactUsername`, `contactAccountId` | Send to a contact's bank account |
| `transfer_contact_wallet` | `contactUsername`, `contactAccountId` | Send to a contact's wallet       |

**Response**

```json theme={null}
{
  "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/{id}/status

Check the status of a payment request. Scope: `write:payments`

**Path parameters**

| Parameter | Type   | Required | Description               |
| --------- | ------ | -------- | ------------------------- |
| `id`      | string | Yes      | Payment request ID (UUID) |

**Response**

```json theme={null}
{
  "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`

***

### 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`

<Warning>
  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`.
</Warning>

**Request body**

```json theme={null}
{
  "amount": "0.01",
  "currency": "USD",
  "payTo": "0x1234567890123456789012345678901234567890",
  "resource": "https://api.example.com/data",
  "network": "base",
  "reference": "API call"
}
```

| Field               | Required | Description                                                       |
| ------------------- | -------- | ----------------------------------------------------------------- |
| `amount`            | Yes      | Amount to pay (e.g. `"0.01"`)                                     |
| `currency`          | Yes      | `USD` (→ USDC) or `EUR` (→ EURC)                                  |
| `payTo`             | Yes      | Recipient wallet address from the 402 `paymentRequirements.payTo` |
| `resource`          | Yes      | The URL that returned HTTP 402                                    |
| `network`           | Yes      | x402 network — must match the Moneda deployment (`base`)          |
| `maxAmountRequired` | No       | Maximum amount from the 402 response                              |
| `reference`         | No       | Optional memo (max 140 chars)                                     |

**Response**

```json theme={null}
{
  "paymentRequestId": "pr_abc123",
  "status": "PENDING_APPROVAL",
  "amount": "0.01",
  "currency": "USD",
  "recipient": "x402 · 0x1234...7890 · api.example.com",
  "paymentType": "x402",
  "expiresAt": "2026-03-07T10:35:00.000Z",
  "message": "x402 payment request created. Approve it in your Moneda app.",
  "pollAfterMs": 10000
}
```

***

## Payment Routing

<Info>
  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.
</Info>

### 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**

| Parameter       | Type   | Required | Description                                        |
| --------------- | ------ | -------- | -------------------------------------------------- |
| `countrySymbol` | string | No       | Filter 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**

| Parameter      | Type   | Required | Description                                                        |
| -------------- | ------ | -------- | ------------------------------------------------------------------ |
| `fromCurrency` | string | Yes      | Source currency (e.g. `USDC`, `EUR`)                               |
| `toCurrency`   | string | Yes      | Target currency                                                    |
| `amountIn`     | string | One of   | Amount the sender pays (mutually exclusive with `amountOut`)       |
| `amountOut`    | string | One of   | Amount the recipient receives (mutually exclusive with `amountIn`) |

***

## Receipts

<Info>
  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.
</Info>

### 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)

<Info>
  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.
</Info>

### 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)

<Info>
  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.
</Info>

### 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)

<Info>
  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.
</Info>

### 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

<Info>
  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.
</Info>

### 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

<Info>
  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.
</Info>

### 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`

***

## Agent Sessions

<Info>
  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).
</Info>

### 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**

```json theme={null}
{
  "granted": true,
  "txHash": "0x4a3f…",
  "paidAmount": 50.0,
  "paidCurrency": "USDC",
  "budgetUsd": "47.50",
  "creditBalanceUsd": "47.50"
}
```

### 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**

```json theme={null}
{
  "monthlySpendLimitUsd": "50.00",
  "unlimited": false
}
```

***

## Error Responses

All errors follow a consistent format:

```json theme={null}
{
  "code": "UNAUTHORIZED",
  "message": "Invalid or missing Bearer token",
  "retryable": false
}
```

**Error codes**

| Code                  | HTTP Status | Description                                |
| --------------------- | ----------- | ------------------------------------------ |
| `UNAUTHORIZED`        | 401         | Invalid or missing Bearer token            |
| `INSUFFICIENT_SCOPE`  | 403         | Token lacks the required scope             |
| `NOT_FOUND`           | 404         | Resource not found                         |
| `RESOURCE_NOT_OWNED`  | 403         | Resource belongs to another user           |
| `VALIDATION_ERROR`    | 422         | Invalid request parameters                 |
| `RATE_LIMIT_EXCEEDED` | 429         | Too many requests (60/min per IP)          |
| `INTERNAL_ERROR`      | 500         | Unexpected server error                    |
| `SERVICE_UNAVAILABLE` | 503         | Downstream service temporarily unavailable |

***

## What's next?

<CardGroup cols={2}>
  <Card title="Authentication" icon="lock" href="/api/authentication">
    Learn how to authenticate your API requests.
  </Card>

  <Card title="OpenAPI Spec" icon="file-code" href="/api/openapi">
    Explore the API interactively with Swagger UI.
  </Card>

  <Card title="Scopes" icon="list-check" href="/scopes">
    See which scopes each endpoint requires.
  </Card>

  <Card title="CLI Tool" icon="terminal" href="/cli/overview">
    Use these endpoints from the command line with the Moneda CLI.
  </Card>
</CardGroup>
