# Wallets

A **wallet** is Fynex's per-currency balance ledger for a seller account. Each seller can hold multiple wallets — one per currency — and every settlement, payout, and split execution produces append-only ledger entries on the relevant wallet.

> [!NOTE]
> **Two ways to read wallets.** This guide documents the **GraphQL** surface (`POST /dashboard/graphql`, cookie-session authenticated), which exposes the full ledger including individual `WalletEntry` rows and `WalletTransfer` records. There is also a **seller-bearer-token REST** surface for integrators who only need balances and transactions:
> - `GET /payments-api/v1/wallets` — list the seller's wallets with balance snapshots
> - `GET /payments-api/v1/wallets/{wallet_id}` — a single wallet
> - `GET /payments-api/v1/wallets/{wallet_id}/transactions` — that wallet's transactions
>
> The REST surface uses your `Authorization: Bearer <seller_token>` (no cookie), is seller-scoped, hides Fynex system wallets, and returns balances as `availableBalanceMinor` / `heldBalanceMinor` / `pendingBalanceMinor` / `totalBalanceMinor` (minor units). See [GraphQL Authentication](https://api.fynex.ai/payments-api/v2/docs#tag/graphql-auth) for the cookie flow used by the GraphQL queries below.

---

## Wallet types

The `WalletType` enum describes the role of each wallet:

| Type | Purpose |
|------|---------|
| `MAIN` | Primary seller balance — most payments settle here |
| `SELLER_OPERATIONAL` | Operational float held by the seller |
| `PAYOUT` | Funds earmarked for outgoing payouts |
| `PLATFORM_COMMISSION` | Fynex platform fee collection wallet |
| `TAX` | Tax reserve wallet |
| `OTHER` | Custom-purpose wallet |
| `EXTERNAL_SAFEGUARDING_ACCOUNT` | Funds held in a safeguarding account |
| `FYNEX_OPERATIONAL` | Internal Fynex operational ledger |
| `EXTERNAL_CREDIT` | Credit facility wallet |

Wallet status can be `ACTIVE`, `INACTIVE`, or `FROZEN`.

---

## GraphQL operations

### List all wallets for a seller

```graphql
query ListWallets($merchantId: ID, $limit: Int, $offset: Int) {
  wallets(merchantId: $merchantId, limit: $limit, offset: $offset) {
    id
    name
    type
    status
    currencyCode
    currencyScale
    isSystem
    createdAt
    snapshot {
      availableBalanceMinor
      heldBalanceMinor
      pendingBalanceMinor
      updatedAt
    }
  }
}
```

### Get a single wallet with ledger entries

```graphql
query GetWallet($id: Int!, $entryLimit: Int, $entryOffset: Int) {
  wallet(id: $id) {
    id
    name
    type
    status
    currencyCode
    currencyScale
    snapshot {
      availableBalanceMinor
      heldBalanceMinor
      pendingBalanceMinor
    }
    entries(limit: $entryLimit, offset: $entryOffset) {
      id
      entrySeq
      direction
      kind
      amountMinor
      availableDeltaMinor
      heldDeltaMinor
      balanceBeforeMinor
      balanceAfterMinor
      occurredAt
    }
  }
}
```

---

## Type reference

### `Wallet`

| Field | Type | Description |
|-------|------|-------------|
| `id` | `Int!` | Numeric wallet ID |
| `payeeId` | `Int!` | Payee this wallet belongs to |
| `sellerAccountId` | `Int!` | Owning seller account |
| `type` | `WalletType!` | Wallet role (see table above) |
| `status` | `WalletStatus!` | `ACTIVE`, `INACTIVE`, or `FROZEN` |
| `name` | `String!` | Human-readable label |
| `currencyCode` | `CurrencyCode!` | ISO 4217 currency code |
| `currencyScale` | `Int!` | Decimal scale (e.g. `2` for EUR meaning amounts are in cents) |
| `isSystem` | `Boolean!` | `true` for Fynex-managed system wallets |
| `createdAt` | `Time!` | Creation timestamp |
| `snapshot` | `WalletBalanceSnapshot` | Latest balance snapshot |
| `entries` | `[WalletEntry!]!` | Paginated ledger entries |
| `payoutMethod` | `PayoutMethod` | Associated payout method if any |

### `WalletBalanceSnapshot`

The snapshot is updated after each ledger write and represents the current balance at the time of the last entry.

| Field | Type | Description |
|-------|------|-------------|
| `availableBalanceMinor` | `Int!` | Spendable balance in minor currency units |
| `heldBalanceMinor` | `Int!` | Funds on hold (pending authorization) |
| `pendingBalanceMinor` | `Int!` | Funds in transit |
| `lastEntrySeq` | `Int!` | Sequence number of the entry that produced this snapshot |
| `updatedAt` | `Time!` | When the snapshot was last written |

All balance amounts are in **minor currency units** (e.g. cents for EUR/USD). Divide by `10^currencyScale` to get the decimal amount.

### `WalletEntry`

Each entry is an append-only record of a balance movement.

| Field | Type | Description |
|-------|------|-------------|
| `id` | `Int!` | Entry ID |
| `entrySeq` | `Int!` | Monotonically increasing per-wallet sequence |
| `direction` | `WalletEntryDirection!` | `DEBIT` or `CREDIT` |
| `kind` | `WalletEntryKind!` | Cause: `PAYMENT`, `PAYOUT`, `SPLIT_EXECUTION`, `MANUAL_ADJUST`, `INCOMING_PAYMENT_PROCESSED`, `OUTGOING_PAYMENT_PROCESSED` |
| `amountMinor` | `Int!` | Movement amount in minor units |
| `balanceBeforeMinor` | `Int!` | Available balance before this entry |
| `balanceAfterMinor` | `Int!` | Available balance after this entry |
| `heldDeltaMinor` | `Int!` | Change in held balance |
| `occurredAt` | `Time!` | When the movement occurred |
| `transferId` | `Int` | Linked `WalletTransfer` ID if this entry resulted from an inter-wallet movement |
| `reconciledAt` | `Time` | Set when the entry is reconciled |

### `WalletTransfer`

A transfer records an inter-wallet movement (e.g. when a split rule distributes funds from the main wallet to a payee wallet).

| Field | Type | Description |
|-------|------|-------------|
| `id` | `Int!` | Transfer ID |
| `type` | `String!` | Transfer type |
| `status` | `String!` | Current status |
| `sourceWalletId` | `Int!` | Origin wallet |
| `destinationWalletId` | `Int` | Target wallet (if intra-Fynex) |
| `destinationPayoutMethodId` | `Int` | Target payout method (if external) |
| `sourceAmountMinor` | `Int!` | Amount debited from source |
| `destinationAmountMinor` | `Int!` | Amount credited to destination |
| `sourceCurrencyCode` | `CurrencyCode!` | Source currency |
| `destinationCurrencyCode` | `CurrencyCode!` | Destination currency |
| `splitExecutionId` | `Int` | Linked split execution if applicable |
| `requestedAt` | `Time` | When the transfer was requested |
| `postedAt` | `Time` | When the transfer was posted |
| `errorCode` | `String` | Present if the transfer failed |

---

## Code samples

#### curl

```bash
# Step 1 — login and save the cookie
curl -sc cookies.txt \
  -X POST https://api.fynex.ai/api/v1/login/dashboard \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com", "password": "your_password"}'

# Step 2 — list wallets for a seller account
curl -b cookies.txt \
  -X POST https://api.fynex.ai/dashboard/graphql \
  -H "Content-Type: application/json" \
  -d '{
    "query": "query ListWallets($merchantId: ID, $limit: Int, $offset: Int) { wallets(merchantId: $merchantId, limit: $limit, offset: $offset) { id name type status currencyCode snapshot { availableBalanceMinor heldBalanceMinor pendingBalanceMinor updatedAt } } }",
    "variables": { "merchantId": "42", "limit": 20, "offset": 0 }
  }'
```

#### JavaScript

```js
const BASE = 'https://api.fynex.ai';

async function login(email, password) {
  const res = await fetch(`${BASE}/api/v1/login/dashboard`, {
    method: 'POST',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email, password }),
  });
  if (!res.ok) throw new Error(`Login failed: ${res.status}`);
}

async function gql(query, variables = {}) {
  const res = await fetch(`${BASE}/dashboard/graphql`, {
    method: 'POST',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ query, variables }),
  });
  const { data, errors } = await res.json();
  if (errors?.length) throw new Error(errors[0].message);
  return data;
}

await login('you@example.com', 'your_password');

const { wallets } = await gql(
  `query ListWallets($merchantId: ID, $limit: Int, $offset: Int) {
     wallets(merchantId: $merchantId, limit: $limit, offset: $offset) {
       id
       name
       type
       status
       currencyCode
       snapshot {
         availableBalanceMinor
         heldBalanceMinor
         pendingBalanceMinor
         updatedAt
       }
     }
   }`,
  { merchantId: '42', limit: 20, offset: 0 }
);

for (const w of wallets) {
  const scale = Math.pow(10, 2); // adjust if currencyScale != 2
  console.log(
    `${w.name} (${w.currencyCode}): ${w.snapshot.availableBalanceMinor / scale} available`
  );
}
```

#### Python

```python
import requests

BASE = "https://api.fynex.ai"
session = requests.Session()

# Step 1 — login
session.post(
    f"{BASE}/api/v1/login/dashboard",
    json={"email": "you@example.com", "password": "your_password"},
).raise_for_status()

# Step 2 — list wallets
query = """
  query ListWallets($merchantId: ID, $limit: Int, $offset: Int) {
    wallets(merchantId: $merchantId, limit: $limit, offset: $offset) {
      id
      name
      type
      status
      currencyCode
      currencyScale
      snapshot {
        availableBalanceMinor
        heldBalanceMinor
        pendingBalanceMinor
        updatedAt
      }
    }
  }
"""

resp = session.post(
    f"{BASE}/dashboard/graphql",
    json={"query": query, "variables": {"sellerAccountId": 42, "limit": 20, "offset": 0}},
)
resp.raise_for_status()
result = resp.json()
if "errors" in result:
    raise RuntimeError(result["errors"][0]["message"])

for wallet in result["data"]["wallets"]:
    scale = 10 ** wallet["currencyScale"]
    available = wallet["snapshot"]["availableBalanceMinor"] / scale
    print(f'{wallet["name"]} ({wallet["currencyCode"]}): {available:.2f} available')
```

---

## Use cases

**Check available balance before requesting a payout.** Query the wallet's `snapshot.availableBalanceMinor`, convert to decimal using `currencyScale`, and compare against the desired payout amount. If `availableBalanceMinor` is insufficient, the payout request will be rejected.

**Reconcile balances against your own ledger.** Fetch `entries` with `limit`/`offset` pagination, ordering by `entrySeq`. Each entry's `balanceAfterMinor` should match your internal running balance. Use `reconciledAt` to distinguish entries already reconciled by Fynex.

> [!CAUTION]
> Balance amounts are always in **minor currency units** (e.g. cents). A value of `12345` with `currencyScale: 2` equals 123.45 in the nominal currency. Never display raw minor-unit amounts to end users without dividing by `10^currencyScale`.

## See also

- **[GraphQL Authentication](https://api.fynex.ai/payments-api/v2/docs#tag/graphql-auth)** — How to obtain the dashboard_session cookie required for all GraphQL calls.
- **[Reconciliation](https://api.fynex.ai/payments-api/v2/docs#tag/reconciliation)** — Match Fynex settlement records against your own books.
- **[Payouts](https://api.fynex.ai/payments-api/v2/docs#tag/payouts)** — Request a wallet-to-payee transfer via REST or GraphQL.
