> ## Documentation Index
> Fetch the complete documentation index at: https://docs.truv.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Financial Accounts Scenarios

> Pair a bank login with the accounts, balances, transactions, holdings, and statements Truv should return for it

Set the bank data a sandbox verification returns. A `financial_accounts` scenario pairs a login with the accounts you want back, each with balances, owners, transactions, investment holdings, statements, and recurring deposits, and serves them when the user signs in to a Truv test bank with that login. It drives Verification of Assets, cashflow transactions, and the bank-income flows.

Use it when the built-in [assets credentials](https://docs.truv.com/developers/testing/assets.md) don't cover the case you need: a specific balance, a direct deposit cadence your income engine has to recognize, an investment account, or a bank statement PDF with your own numbers. For the shared concepts, read [Custom Sandbox Data](https://docs.truv.com/developers/testing/custom-sandbox-data.md) first.

***

## Create a scenario \[Server-side]

> [!WARNING]
>
> Truv's built-in bank credentials are reserved. The API refuses `goodlogin` and `error.user` as a username, and any username and password pair listed under [Assets Testing](https://docs.truv.com/developers/testing/assets.md), with `reserved_credential`. Use a distinctive username prefix, such as `acme.`.

This request creates a scenario with one checking account that receives a biweekly paycheck:

```bash theme={null}
curl --request POST \
     --url https://prod.truv.com/v1/sandbox/scenarios/ \
     --header 'X-Access-Client-Id: YOUR_TRUV_CLIENT_ID' \
     --header 'X-Access-Secret: YOUR_TRUV_SANDBOX_SECRET' \
     --header 'Content-Type: application/json' \
     --data '{
  "data_source": "financial_accounts",
  "label": "Acme — checking with biweekly payroll",
  "username": "acme.checking",
  "password": "s4ndb0x",
  "payload": {
    "accounts": [
      {
        "external_id": "acct-1",
        "type": "CHECKING",
        "account_number": "1234567890",
        "routing_number": "111000025",
        "nickname": "Everyday Checking",
        "balances": {
          "currency_code": "USD",
          "balance": "5231.44",
          "available_balance": "5100.00"
        },
        "owners": [
          { "full_name": "John Doe", "relation_type": "PRIMARY", "email": "john@acme.com" }
        ],
        "recurring": [
          {
            "description": "ACME PAYROLL DIR DEP",
            "amounts": ["1789.03"],
            "pay_frequency": "BW",
            "start": "2026-02-20",
            "categories": ["Income", "Paycheck"],
            "is_direct_deposit": true
          }
        ]
      }
    ]
  }
}'
```

> [!NOTE]
>
> Always send `"data_source": "financial_accounts"`. `username` and `password` are supported on payroll, financial accounts, and insurance, so Truv can't infer the data source from them and refuses the request with `data_source_required`.

### Use it in Bridge

[Start a sandbox verification](https://docs.truv.com/developers/testing/assets.md) with `product_type: assets` or `transactions`. In Truv Bridge, search for **Truv Bank non-OAuth** and sign in with `acme.checking` / `s4ndb0x`, or pick **Truv Bank OAuth** and enter the same login on the sandbox bank page it opens. The OAuth page then lists the scenario's accounts for the user to select, the way a real OAuth bank does. The task completes with your accounts instead of Truv's built-in test data. Read the result from the [task webhooks](https://docs.truv.com/api-reference/webhooks/object.md), the [assets report](https://docs.truv.com/api-reference/user-asset-verification-reports/object.md), and the [transactions endpoint](https://docs.truv.com/api-reference/transactions/object.md).

The scenario above sets no `provider_id`, so either test bank works. A scenario that sets `provider_id: "truv_bank"` answers only connections through that provider.

***

## Matching fields

| Field | Type | Description |
| - | - | - |
| `username` | string | The login the user types. Matched exactly. |
| `password` | string | The password the user types. Omit it to match on username alone. |
| `provider_id` | string | Narrows the match to one bank provider, e.g. `truv_bank`. |
| `is_refresh` | boolean | `true` matches data refreshes only, `false` initial verifications only. `null` (the default) matches both. An OAuth login page always resolves as an initial verification. |

`products` restricts the scenario to a subset of `assets`, `transactions`, `income`, and `employment`. Empty, the default, matches every product.

### Behavior

A financial accounts scenario accepts every `behavior_status`, and each ends the task with that status except `not_supported`, which ends it with `unavailable`. `behavior_mfa` is replayed at login, and a response that doesn't match a challenge's `answer` or `answers` ends the task with `mfa_error`. See [Behavior](https://docs.truv.com/developers/testing/custom-sandbox-data.md#behavior) for the fields. A scenario with a failure status still shows up on the OAuth sandbox page, with nothing to select, so the task can end the way the scenario says.

***

## How accounts are served

Truv builds the accounts fresh on every run, so the same scenario stays current without edits:

* **Ids are stable.** Each account, owner, and holding gets an id derived from its position in the payload, so a refresh updates the records a previous run created instead of adding a second set. A transaction or statement keeps the `external_id` you give it.
* **`mask` defaults** to the last four digits of `account_number`.
* **`external_updated_at` is the as-of time** for balances and the end of every `recurring` series. It defaults to the time of the run, which is what keeps a deposit history current.
* **Recurring series expand into transactions.** See [Recurring deposits](#recurring-deposits).
* **Incomplete records are dropped, not failed.** A transaction with no `amount` or no date, a statement with no library entry, and an amount outside the supported amount range are left out; the rest of the scenario is still served.
* **`type` and `status` have defaults.** A transaction with no `type` is `DEBIT` when its amount is negative and `CREDIT` otherwise. One with no `status` is `POSTED`. `transacted_at` defaults to `posted_at`.

### Recurring deposits

Each `recurring` rule generates `CREDIT` transactions after `start`, through `external_updated_at`. A date on `start` itself is never generated. The `amounts` repeat in order.

The dates follow a fixed calendar schedule for each `pay_frequency`. They don't count from `start`:

| `pay_frequency` | Schedule | First deposit |
| - | - | - |
| `W` weekly | Every Monday | The first Monday after `start` |
| `BW` biweekly | Every 14 days | The second Monday after `start`, not the first |
| `M` monthly | The last day of each month | The first month end after `start` |
| `SM` semimonthly | The 15th and the last day of each month | The first of those dates after `start`. A `start` on the 13th or 14th skips that month's 15th. |

A `BW` series with `start: "2026-02-20"`, a Friday, has its first deposit on Monday 2026-03-02.

Transactions are served newest first. Authored and generated transactions share one per-account [limit](#limits), and the most recent ones are kept.

***

## Payload reference

The `payload` object contains an `accounts` array. Every field is optional unless noted: send only what your test needs, and leave the rest out.

> [accordion] accounts\[]
>
> | Field | Type | Description |
> | - | - | - |
> | `external_id` | string | Your own identifier for the account. |
> | `type` | string | **Required.** `CHECKING`, `SAVINGS`, `LOAN`, `CREDIT_CARD`, `INVESTMENT`, `LINE_OF_CREDIT`, `MORTGAGE`, `PROPERTY`, `CASH`, `INSURANCE`, `PREPAID`, `CHECKING_LINE_OF_CREDIT`. |
> | `subtype` | string | One of the account subtypes, e.g. `MONEY_MARKET`, `CERTIFICATE_OF_DEPOSIT`, `AUTO`, `STUDENT`, `HELOC`, `PLAN_401_K`, `IRA`, `ROTH_IRA`, `BROKERAGE`, `HSA`. The [API reference](https://docs.truv.com/api-reference/sandbox/v1_sandbox_scenarios_create.md) lists all of them. |
> | `account_number` | string | Full account number. Also served as the account's ACH payment details. |
> | `routing_number` | string | Routing number. Truv can restrict your account to a fixed set of test routing numbers; without that restriction, any value is accepted. |
> | `mask` | string | Four characters. Defaults to the last four of `account_number`. |
> | `nickname` | string | |
> | `balances` | object | `currency_code`, `balance`, `available_balance`, `credit_limit`. |
> | `external_updated_at` | datetime | Balance as-of time and the end of every recurring series. Defaults to the time of the run. |
> | `holdings_value` | decimal string | Total value of holdings, for investment accounts. |
> | `owners` | array | Account owners. |
> | `transactions` | array | Individual transactions. |
> | `holdings` | array | Investment holdings. |
> | `statements` | array | Statements, each backed by a library entry. |
> | `recurring` | array | Recurring series expanded into transactions. |

> [accordion] accounts\[].owners\[]
>
> | Field | Type | Description |
> | - | - | - |
> | `full_name` | string | Split into first and last name when served. |
> | `relation_type` | string | e.g. `PRIMARY`. |
> | `email` | string | |
> | `phone` | string | |
> | `address` | object | `street`, `city`, `state`, `zip`, `country`. |

> [accordion] accounts\[].transactions\[]
>
> | Field | Type | Description |
> | - | - | - |
> | `external_id` | string | Generated when omitted. |
> | `amount` | decimal string | Signed. Negative for money out, positive for money in. A transaction with no amount is dropped. |
> | `currency_code` | string | ISO 4217 code. |
> | `type` | string | `DEBIT`, `CREDIT`, `MEMO`. Defaults from the sign of `amount`. |
> | `status` | string | `POSTED`, `PENDING`, `AUTHORIZATION`, `MEMO`. Default `POSTED`. |
> | `description` | string | As the bank shows it. |
> | `categories` | array | Category labels, up to 100 characters each. |
> | `posted_at` | date | |
> | `transacted_at` | date | Defaults to `posted_at`. A transaction with neither date is dropped. |
> | `memo` | string | |
> | `check_number` | string | |
> | `merchant_name` | string | |
> | `merchant_category_code` | string | Digits, e.g. `"5814"`. |
> | `location` | object | `latitude`, `longitude`. |

> [accordion] accounts\[].holdings\[]
>
> | Field | Type | Description |
> | - | - | - |
> | `symbol` | string | Ticker. |
> | `description` | string | Name of the security. |
> | `holding_type` | string | e.g. `EQUITY`, `EXCHANGE_TRADED_FUND`, `MUTUAL_FUND`, `MONEY_MARKET`, `FIXED_INCOME`, `CASH`, `CERTIFICATE_OF_DEPOSIT`. The [API reference](https://docs.truv.com/api-reference/sandbox/v1_sandbox_scenarios_create.md) lists all of them. |
> | `quantity` | decimal string | Units held. |
> | `currency_code` | string | |
> | `current_price` | decimal string | |
> | `current_price_as_of` | date | |
> | `purchase_price` | decimal string | |
> | `cost_basis` | decimal string | |
> | `market_value` | decimal string | |
> | `daily_change` | decimal string | |
> | `total_ugl_amount` | decimal string | Unrealized gain or loss. |
> | `total_ugl_percentage` | decimal string | |
> | `face_value` | decimal string | For bonds. |
> | `rate` | decimal string | For fixed income. |

> [accordion] accounts\[].statements\[]
>
> | Field | Type | Description |
> | - | - | - |
> | `external_id` | string | Generated when omitted. |
> | `statement_date` | date | **Required.** |
> | `document_id` | string | **Required.** Library entry served as the statement file. See [Attach statement PDFs](#attach-statement-pdfs). |

> [accordion] accounts\[].recurring\[]
>
> | Field | Type | Description |
> | - | - | - |
> | `description` | string | **Required.** Carried by every generated transaction. |
> | `amounts` | array | **Required**, at least one decimal string. Cycled through in order. |
> | `pay_frequency` | string | **Required.** `M` monthly, `SM` semimonthly, `W` weekly, `BW` biweekly. Annual, semiannual, and commission can't be placed on a grid and are refused. |
> | `start` | date | **Required.** Generated dates begin with the first grid date after it. See [Recurring deposits](#recurring-deposits) for each frequency's schedule. |
> | `categories` | array | Carried by every generated transaction. |
> | `is_direct_deposit` | boolean | Default `false`. Marks the generated transactions as direct deposits. |

> [!NOTE]
>
> Money and quantities are decimal **strings**, so nothing is lost to floating point: `"5231.44"`, not `5231.44`.

***

## Examples

Each example is a request body for `POST /v1/sandbox/scenarios/` and creates a new scenario. Send it with the same `curl` command as [the first scenario](#create-a-scenario-server-side).

### Two paychecks and rent

Add two deposit series on one account to give the bank income engine two employers to find, and a monthly rent debit alongside them. The amounts on the second series alternate, so every other deposit is larger.

```json theme={null}
{
  "data_source": "financial_accounts",
  "label": "Two jobs — payroll plus gig income",
  "username": "acme.twoincomes",
  "password": "s4ndb0x",
  "payload": {
    "accounts": [
      {
        "type": "CHECKING",
        "account_number": "1234567890",
        "routing_number": "111000025",
        "nickname": "Everyday Checking",
        "balances": { "currency_code": "USD", "balance": "3870.15", "available_balance": "3870.15" },
        "owners": [{ "full_name": "John Doe" }],
        "recurring": [
          {
            "description": "ACME PAYROLL DIR DEP",
            "amounts": ["1789.03"],
            "pay_frequency": "BW",
            "start": "2026-01-09",
            "categories": ["Income", "Paycheck"],
            "is_direct_deposit": true
          },
          {
            "description": "RIDESHARE CO PAYOUT",
            "amounts": ["412.50", "538.20"],
            "pay_frequency": "W",
            "start": "2026-03-02",
            "categories": ["Income"],
            "is_direct_deposit": true
          }
        ],
        "transactions": [
          {
            "amount": "-1650.00",
            "description": "SUNSET APARTMENTS RENT",
            "categories": ["Bills & Utilities", "Rent"],
            "posted_at": "2026-08-01"
          },
          {
            "amount": "-1650.00",
            "description": "SUNSET APARTMENTS RENT",
            "categories": ["Bills & Utilities", "Rent"],
            "posted_at": "2026-07-01"
          }
        ]
      }
    ]
  }
}
```

### Savings and an investment account

Add more accounts to the `accounts` array. Up to 10 fit in one scenario. An `INVESTMENT` account carries `holdings`.

```json theme={null}
{
  "data_source": "financial_accounts",
  "label": "Assets — checking, savings, brokerage",
  "username": "acme.assets",
  "password": "s4ndb0x",
  "products": ["assets"],
  "payload": {
    "accounts": [
      {
        "type": "CHECKING",
        "account_number": "1234567890",
        "nickname": "Everyday Checking",
        "balances": { "currency_code": "USD", "balance": "5231.44", "available_balance": "5100.00" },
        "owners": [{ "full_name": "John Doe" }]
      },
      {
        "type": "SAVINGS",
        "subtype": "MONEY_MARKET",
        "account_number": "2234567890",
        "nickname": "Emergency Fund",
        "balances": { "currency_code": "USD", "balance": "24000.00", "available_balance": "24000.00" },
        "owners": [{ "full_name": "John Doe" }]
      },
      {
        "type": "INVESTMENT",
        "subtype": "BROKERAGE",
        "account_number": "3234567890",
        "nickname": "Brokerage",
        "balances": { "currency_code": "USD", "balance": "6001.25" },
        "holdings_value": "6001.25",
        "owners": [{ "full_name": "John Doe" }],
        "holdings": [
          {
            "symbol": "VOO",
            "description": "Vanguard S&P 500 ETF",
            "holding_type": "EXCHANGE_TRADED_FUND",
            "quantity": "12.5",
            "current_price": "480.10",
            "current_price_as_of": "2026-08-21",
            "cost_basis": "5125.00",
            "market_value": "6001.25"
          }
        ]
      }
    ]
  }
}
```

### Failing login

A failure status needs no payload. Use it to exercise the error paths in your integration.

```json theme={null}
{
  "data_source": "financial_accounts",
  "label": "Bank under maintenance",
  "username": "acme.maintenance",
  "password": "s4ndb0x",
  "behavior_status": "unavailable",
  "behavior_error_message": "Online banking is unavailable until 6 AM ET.",
  "payload": {}
}
```

### MFA challenge

`behavior_mfa.challenges` replays one screen per entry, in order, on both test banks. Any code other than `answer` ends the task with `mfa_error`.

```json theme={null}
{
  "data_source": "financial_accounts",
  "label": "Bank — SMS code",
  "username": "acme.bankmfa",
  "password": "s4ndb0x",
  "behavior_mfa": {
    "challenges": [
      { "type": "TEXT", "label": "Enter the code we texted you", "answer": "12345" }
    ]
  },
  "payload": {
    "accounts": [
      {
        "type": "CHECKING",
        "balances": { "currency_code": "USD", "balance": "5231.44" }
      }
    ]
  }
}
```

### Refresh-only data

Restrict a scenario to refreshes with `is_refresh: true` to set the data a [data refresh](https://docs.truv.com/developers/integration/embedded-orders/data-refresh.md) should find. Create it alongside the `acme.checking` scenario with the same login; on a refresh it wins, because it sets `is_refresh` and the other doesn't.

```json theme={null}
{
  "data_source": "financial_accounts",
  "label": "Acme — checking, after a large withdrawal",
  "username": "acme.checking",
  "password": "s4ndb0x",
  "is_refresh": true,
  "payload": {
    "accounts": [
      {
        "type": "CHECKING",
        "balances": { "currency_code": "USD", "balance": "1231.44" }
      }
    ]
  }
}
```

***

## Attach statement PDFs

Each statement references an uploaded PDF by `document_id`, the `id` of a [document library](https://docs.truv.com/developers/testing/custom-sandbox-data.md#document-library) entry. Upload the PDF once, then reference its `id`.

```bash theme={null}
curl --request POST \
     --url https://prod.truv.com/v1/sandbox/documents/ \
     --header 'X-Access-Client-Id: YOUR_TRUV_CLIENT_ID' \
     --header 'X-Access-Secret: YOUR_TRUV_SANDBOX_SECRET' \
     --form 'filename=acme-statement-2026-07.pdf' \
     --form 'file=@./acme-statement-2026-07.pdf'
```

The response is the library entry, abbreviated here; the scenario below references its `id` as the statement's `document_id`.

```json theme={null}
{
  "id": "6a1c3e5b9d7f4a2c8e0b4d6f2a8c1e73",
  "uploaded_file": { "file_id": "6a1c3e5b9d7f4a2c8e0b4d6f2a8c1e73", "filename": "acme-statement-2026-07.pdf", "...": "..." },
  "documents": ["..."],
  "created_at": "2026-09-17T10:11:02.104882Z",
  "updated_at": "2026-09-17T10:11:02.104901Z"
}
```

Then put that `id` on the statement. Abbreviated: the account carries whatever other data your test needs.

```json theme={null}
{
  "data_source": "financial_accounts",
  "label": "Checking with statements",
  "username": "acme.statements",
  "password": "s4ndb0x",
  "payload": {
    "accounts": [
      {
        "type": "CHECKING",
        "account_number": "1234567890",
        "balances": { "currency_code": "USD", "balance": "5231.44" },
        "statements": [
          { "external_id": "stmt-2026-07", "statement_date": "2026-07-31", "document_id": "6a1c3e5b9d7f4a2c8e0b4d6f2a8c1e73" }
        ]
      }
    ]
  }
}
```

The completed task serves the PDF through [Bank Statements](https://docs.truv.com/api-reference/bank-statements/link-bank-statements.md), the same as any other connection.

***

## Limits

These are the default limits for financial accounts payloads. The account-wide limits are listed on [Custom Sandbox Data](https://docs.truv.com/developers/testing/custom-sandbox-data.md#limits).

| Limit | Default |
| - | - |
| Accounts per scenario | 10 |
| Transactions per account, authored and generated together | 300 |
| Holdings per account | 50 |
| Owners per account | 4 |
| Recurring series per account | 10 |
| Statements per account | 24 |

***

## Next steps

> [card] [Assets Testing](https://docs.truv.com/developers/testing/assets.md)
>
> Truv's built-in bank credentials and sample reports

> [card] [Payroll scenarios](https://docs.truv.com/developers/testing/custom-sandbox-data/payroll.md)
>
> Pair the same user with a payroll login

> [card] [Data refresh](https://docs.truv.com/developers/integration/embedded-orders/data-refresh.md)
>
> How refreshes reach your `is_refresh` scenarios

> [card] [Sandbox API reference](https://docs.truv.com/api-reference/sandbox/object.md)
>
> Full request and response schemas for every sandbox endpoint


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.