Virtual Accounts
Inspect dashboard virtual accounts and understand the separate virtual-account issuance preview.
A virtual account is an issued IBAN that lets customers pay a seller by bank transfer without exposing the seller's primary bank account.
Caution
The
virtualAccountsandvirtualAccountqueries are not exposed on/dashboard/graphql. They exist on the Fynex staff surface only, backed by the banking-provider integration; adashboard_sessioncalling them receivesCannot query field "virtualAccounts" on type "Query". They are documented below for reference — ask your Fynex representative for account listings until a seller-facing read ships. The REST issuance preview described below creates a different account record and is not returned by those queries either.
When to use virtual accounts
- Collect bank-transfer payments without exposing the seller's primary IBAN.
- Isolate funds per customer — issue one virtual account per customer and reconcile inflows by IBAN.
- Reconcile provider account activity using the provider-specific account surface enabled for the seller.
Provider boundaries
- The GraphQL operations below list and inspect banking-provider-backed virtual accounts.
POST /api/v1/accounts/virtualis a tier-gated issuance preview. It currently returns only an internal Fynex UUID.- There is no seller-facing read/status endpoint for issued accounts in this API version, and the GraphQL operations below cannot resolve the UUID returned by REST issuance.
- Provider settlement events update the issued account/payment records, but they do not currently create seller wallet-ledger entries. Do not use wallet polling as confirmation of an incoming transfer.
Use the issuance preview only as part of a coordinated rollout with Fynex. Do not present its returned UUID as an IBAN or build automated account-status or incoming-funds reconciliation until the corresponding seller read and wallet-ledger surfaces are released.
GraphQL operations
Caution
Staff surface only — not callable with a
dashboard_session. Shown for reference.
| Operation | Signature |
|---|---|
| List virtual accounts | virtualAccounts(first: Int, after: String, type: VirtualAccountType): VirtualAccountConnection! |
| Get a single account | virtualAccount(id: ID!): VirtualAccount |
VirtualAccountType enum
| Value | Description |
|---|---|
CLIENT |
Account issued for a specific customer / payer |
OPERATING |
Account used for the seller's own operating funds |
Type reference
VirtualAccount
| Field | Type | Description |
|---|---|---|
id |
ID! |
Internal Fynex account ID |
accountId |
String! |
The issued IBAN or account number to share with the payer |
virtualAccountId |
String! |
Banking partner's own reference for this account |
currency |
Currency! |
Account currency |
type |
VirtualAccountType! |
CLIENT or OPERATING |
status |
String! |
Current provisioning status |
balance |
Float! |
Current balance (in major currency units) |
createdAt |
String! |
ISO timestamp of account creation |
customerInfo |
VirtualAccountCustomer |
Optional customer metadata (name, email, phone, address) |
VirtualAccountConnection
Cursor-based pagination is used for virtualAccounts:
| Field | Type | Description |
|---|---|---|
edges |
[VirtualAccountEdge!]! |
Paginated account nodes |
pageInfo |
PageInfo! |
Cursor and page metadata |
PageInfo fields: hasNextPage, hasPreviousPage, startCursor, endCursor, totalCount, currentPage, totalPages.
Advance pages by passing the endCursor value as the after argument on the next call.
Requesting a virtual account
Caution
Tier-gated preview.
POST /api/v1/accounts/virtualmay not be enabled for all seller tiers. It does not yet provide a public status/read flow. Confirm the rollout and reconciliation process with Fynex before using it.
POST /api/v1/accounts/virtual
Content-Type: application/json
Authorization: Bearer <seller-api-token>
Idempotency-Key: va_customer-jane_2026-08-08
{
"name": "Jane Smith"
}
The seller is derived from the bearer token; the request does not accept a merchant or seller ID. A successful request returns 201 Created:
{
"accountId": "0f834fa8-f5ab-4d80-96b1-fc2d9e4e1824"
}
accountId is the internal Fynex UUID for the issuance record, not the issued IBAN. Retrying the same request with the same Idempotency-Key returns the same UUID without issuing another account. Reusing that key with a different name is rejected. Keep the key stable until the request has a definitive response. The dashboard GraphQL queries continue to use the dashboard cookie, but they do not expose this issuance record; REST issuance uses seller bearer authentication.
Code samples — list virtual accounts
curl
# Step 1 — login
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 virtual accounts (first 20, CLIENT type)
curl -b cookies.txt \
-X POST https://api.fynex.ai/dashboard/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "# Not available on /dashboard/graphql — staff surface only\nquery ListVAs($first: Int, $after: String, $type: VirtualAccountType) { virtualAccounts(first: $first, after: $after, type: $type) { pageInfo { hasNextPage endCursor totalCount } edges { cursor node { id accountId virtualAccountId currency type status balance createdAt customerInfo { name email } } } } }",
"variables": { "first": 20, "type": "CLIENT" }
}'
JavaScript
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');
// Fetch all CLIENT-type virtual accounts using cursor pagination
let after = undefined;
let allAccounts = [];
do {
const { virtualAccounts } = await gql(
`# Not available on /dashboard/graphql — staff surface only
query ListVAs($first: Int, $after: String, $type: VirtualAccountType) {
virtualAccounts(first: $first, after: $after, type: $type) {
pageInfo {
hasNextPage
endCursor
totalCount
}
edges {
node {
id
accountId
virtualAccountId
currency
type
status
balance
createdAt
customerInfo {
name
email
}
}
}
}
}`,
{ first: 20, after, type: 'CLIENT' }
);
allAccounts = allAccounts.concat(virtualAccounts.edges.map((e) => e.node));
after = virtualAccounts.pageInfo.hasNextPage ? virtualAccounts.pageInfo.endCursor : undefined;
} while (after);
console.log(`Fetched ${allAccounts.length} virtual accounts`);
for (const va of allAccounts) {
console.log(`${va.accountId} (${va.currency}) — status: ${va.status}, balance: ${va.balance}`);
}
Python
import requests
BASE = "https://api.fynex.ai"
session = requests.Session()
session.post(
f"{BASE}/api/v1/login/dashboard",
json={"email": "you@example.com", "password": "your_password"},
).raise_for_status()
query = """
# Not available on /dashboard/graphql — staff surface only
query ListVAs($first: Int, $after: String, $type: VirtualAccountType) {
virtualAccounts(first: $first, after: $after, type: $type) {
pageInfo {
hasNextPage
endCursor
totalCount
}
edges {
node {
id
accountId
virtualAccountId
currency
type
status
balance
createdAt
customerInfo {
name
email
}
}
}
}
}
"""
all_accounts = []
after = None
while True:
variables = {"first": 20, "type": "CLIENT"}
if after:
variables["after"] = after
resp = session.post(
f"{BASE}/dashboard/graphql",
json={"query": query, "variables": variables},
)
resp.raise_for_status()
result = resp.json()
if "errors" in result:
raise RuntimeError(result["errors"][0]["message"])
page = result["data"]["virtualAccounts"]
all_accounts.extend(edge["node"] for edge in page["edges"])
if page["pageInfo"]["hasNextPage"]:
after = page["pageInfo"]["endCursor"]
else:
break
print(f"Total virtual accounts: {len(all_accounts)}")
for va in all_accounts:
print(f"{va['accountId']} ({va['currency']}) — {va['status']}, balance: {va['balance']}")
See also
- GraphQL Authentication — Obtain the dashboard_session cookie required for all GraphQL calls.
- Wallets — Inspect wallet-ledger activity for products that post funds to the seller wallet. The issuance preview does not currently create those entries.
- Concepts — The object model behind virtual accounts: sellers, payees, wallets and how funds move between them.