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

# Income & Employment Testing

> Sandbox credentials, scenarios, and sample reports for VOIE and VOE integrations

Test Income & Employment (VOIE) and Employment History (VOE) flows in sandbox using the credentials below. VOIE and VOE share the same payroll credentials — only the `product_type` value on the bridge token or order differs (`income` for VOIE, `employment` for VOE).

In Truv Bridge, search for the **Truv Payroll Provider** to use any of these credentials.

<Tip>
  For additional login fields, use **Phone:** `(111)111-1111` and **Email:** `goodlogin@domain.com`.
</Tip>

***

## Quick start

Use `goodlogin` / `goodpassword` for the happy path. A successful login returns a complete VOIE report with bi-weekly paystubs, an annual income summary, W-2s, and bank account allocations.

| Credential                   | Provider              | Sample report                                                                              |
| ---------------------------- | --------------------- | ------------------------------------------------------------------------------------------ |
| `goodlogin` / `goodpassword` | Truv Payroll Provider | [Bi-weekly VOIE PDF](https://truv.com/wp-content/uploads/2025/11/report-goodlogin.pdf?v=1) |

For the underlying JSON shape, see the [User Income and Employment Report object](/api-reference/user-income-and-employment-reports/object).

**Sample response** — captured from a live sandbox order. Expand a credential to see the response it returns.

<AccordionGroup>
  <Accordion title="goodlogin / goodpassword — full VOIE report shape">
    The complete report a successful payroll connection returns: employment metadata, an annual income summary, per-pay-period statements (one shown; the rest follow the same shape), W-2s, and bank account allocations. Captured from a live sandbox order. Pay-period, earnings, and deduction arrays are truncated here for length.

    ```json theme={null}
    {
      "report_id": "c219d26c1fc7412fb1d0df36a9652600",
      "created_at": "2026-06-18T01:43:32.553366Z",
      "completed_at": "2026-06-18T01:43:32.132883Z",
      "last_task_at": "2026-06-18T01:43:21.714235Z",
      "links": [
        {
          "id": "ac978e05483e48379a5ec82b556d20a0",
          "link_id": "f5ef8b9e1eda4640b38069c9e851bc3a",
          "tracking_info": "8f50b58e35644bb5bff989341eafcdb5",
          "data_source": "payroll",
          "provider": "truv",
          "provider_name": "Truv payroll provider",
          "employments": [
            {
              "id": "e803dc34b8184c4a8ada449b42fccc65",
              "job_title": "PR associate",
              "job_type": "F",
              "start_date": "2023-01-01",
              "end_date": null,
              "external_last_updated": "2026-06-08",
              "is_active": true,
              "manager_name": "Jenny McDouglas",
              "company": {
                "name": "Acme",
                "address": {
                  "zip": "98765",
                  "city": "Anytown",
                  "state": "CA",
                  "street": "123 Main Street",
                  "country": "US"
                },
                "phone": "5555551234",
                "ein": null
              },
              "profile": {
                "id": "e776ba2b32bf4e32aece51c5878d3cdd",
                "created_at": "2026-06-18T01:43:24.784615Z",
                "updated_at": "2026-06-18T01:43:24.784625Z",
                "first_name": "John",
                "last_name": "Doe",
                "full_name": "John Doe",
                "middle_initials": null,
                "email": "john.doe@gmail.com",
                "ssn": "991919991",
                "date_of_birth": "1992-03-03",
                "home_address": {
                  "zip": "94062",
                  "city": "Redwood City",
                  "state": "CA",
                  "street": "35 Dry Ridge Rd",
                  "country": "US"
                }
              },
              "original_hire_date": "2012-02-01",
              "derived_fields": [],
              "missing_data_fields": [],
              "income": "56269.25",
              "income_unit": "YEARLY",
              "pay_rate": "2153.60",
              "pay_frequency": "BW",
              "statements": [
                {
                  "id": "3a5d6a193fc7491689bc62317f4b046f",
                  "pay_date": "2026-06-08",
                  "net_pay": "1629.98",
                  "net_pay_ytd": "19559.76",
                  "gross_pay": "2157.89",
                  "gross_pay_ytd": "25894.68",
                  "bonus": null,
                  "commission": null,
                  "hours": "80.00",
                  "basis_of_pay": "S",
                  "period_start": "2026-05-26",
                  "period_end": "2026-06-08",
                  "regular": "1935.77",
                  "regular_ytd": "23229.24",
                  "bonus_ytd": null,
                  "commission_ytd": null,
                  "overtime": "222.12",
                  "overtime_ytd": "2665.44",
                  "other_pay": null,
                  "other_pay_ytd": null,
                  "earnings": [
                    {
                      "name": "Regular",
                      "category": "regular",
                      "amount": "1935.77",
                      "rate": "26.92",
                      "units": "71.90"
                    },
                    {
                      "name": "Double Time",
                      "category": "overtime",
                      "amount": "161.54",
                      "rate": "53.84",
                      "units": "3.00"
                    },
                    "...and 1 more earnings lines (omitted for brevity)"
                  ],
                  "earnings_ytd": [
                    {
                      "name": "Regular",
                      "category": "regular",
                      "amount": "23229.24",
                      "rate": "26.92",
                      "units": "862.80"
                    },
                    {
                      "name": "Double Time",
                      "category": "overtime",
                      "amount": "1938.48",
                      "rate": "53.84",
                      "units": "36.00"
                    },
                    "...and 1 more earnings ytd lines (omitted for brevity)"
                  ],
                  "deductions": [
                    {
                      "name": "Federal Income Tax",
                      "amount": "153.63",
                      "category": "federal"
                    },
                    {
                      "name": "Social Security Tax",
                      "amount": "133.89",
                      "category": "socialsec"
                    },
                    "...and 4 more deductions lines (omitted for brevity)"
                  ],
                  "deductions_ytd": [
                    {
                      "name": "Federal Income Tax",
                      "amount": "1843.56",
                      "category": "federal"
                    },
                    {
                      "name": "Social Security Tax",
                      "amount": "1606.68",
                      "category": "socialsec"
                    },
                    {
                      "name": "401K Dollars",
                      "amount": "1553.64",
                      "category": "retirement"
                    },
                    {
                      "name": "CA State Income Tax",
                      "amount": "631.44",
                      "category": "state"
                    },
                    {
                      "name": "Medicare Tax",
                      "amount": "375.72",
                      "category": "medicare"
                    },
                    {
                      "name": "Blue Ash Income Tax",
                      "amount": "323.88",
                      "category": "local"
                    }
                  ],
                  "md5sum": "a9b85d17e3583203a854f85a29f78ed5",
                  "file": "https://citadelid-files.s3.amazonaws.com/sandbox/statements/1194546.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAZWHJR5UGKNMOUOTK%2F20260618%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260618T014334Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=6a0374095c4875ba336efeb045805aa914bd502654874de7b125cc127167f2b1",
                  "derived_fields": [],
                  "missing_data_fields": [],
                  "check_number": null
                },
                "...and 53 more pay statements (omitted for brevity)"
              ],
              "annual_income_summary": [
                {
                  "id": "3a5d6a193fc7491689bc62317f4b046f",
                  "year": 2026,
                  "regular": "23229.24",
                  "bonus": null,
                  "commission": null,
                  "overtime": "2665.44",
                  "other_pay": null,
                  "net_pay": "19559.76",
                  "gross_pay": "25894.68"
                },
                {
                  "id": "1385cc45e75c474485c10124e306de8b",
                  "year": 2025,
                  "regular": "50330.02",
                  "bonus": null,
                  "commission": null,
                  "overtime": "5775.12",
                  "other_pay": null,
                  "net_pay": "42379.48",
                  "gross_pay": "56105.14"
                },
                {
                  "id": "2556ebea9d454ea18ddaf85a4143a692",
                  "year": 2024,
                  "regular": "50330.02",
                  "bonus": null,
                  "commission": null,
                  "overtime": "5775.12",
                  "other_pay": null,
                  "net_pay": "42379.48",
                  "gross_pay": "56105.14"
                }
              ],
              "bank_accounts": [
                {
                  "account_number": "11114623",
                  "routing_number": "101014378",
                  "account_name": null,
                  "account_type": "C",
                  "deposit_type": "A",
                  "deposit_value": "1604.98",
                  "bank_name": "Sandbox Bank"
                },
                {
                  "account_number": "11111308",
                  "routing_number": "101013399",
                  "account_name": null,
                  "account_type": "C",
                  "deposit_type": "A",
                  "deposit_value": "25.00",
                  "bank_name": "Sandbox Bank"
                }
              ],
              "w2s": [
                {
                  "file": "https://citadelid-files.s3.amazonaws.com/sandbox/w2s/2560.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAZWHJR5UGKNMOUOTK%2F20260618%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260618T014334Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=7bcd1e3f77c7f8a105a702f5c5745ddfb0dc02bb08cac464f8cc4a341a68a63a",
                  "md5sum": "385320b9c0f851260e86472707a5de18",
                  "year": 2023,
                  "wages": "56105.14",
                  "federal_tax": "3994.38",
                  "social_security_wages": "56105.14",
                  "social_security_tax": "3481.14",
                  "medicare_wages": "56105.14",
                  "medicare_tax": "814.06",
                  "gross_pay": null
                },
                "...and 2 more W-2s (omitted for brevity)"
              ],
              "gse_accepted": true,
              "last_period_end": "2026-06-08",
              "last_pay_date": "2026-06-08"
            }
          ]
        }
      ]
    }
    ```
  </Accordion>
</AccordionGroup>

***

## Credential scenarios

Every credential here connects through the **Truv Payroll Provider** and returns the same report shape as [Quick start](#quick-start) — the **Sample responses** panels show only the fields that differ. The `VOIE-*` scenario ID is a stable test-case identifier you can cite in tickets.

<Note>
  Every scenario below uses the password `goodpassword` — only the username changes. The [error scenarios](#error-scenarios) are the exception: there the password field carries the trigger.
</Note>

### Pay frequency

All four scenarios return SSN `991-91-9991`; only the pay cadence differs.

| Scenario   | Username                 | `pay_frequency`     | Sample report                                                                           |
| ---------- | ------------------------ | ------------------- | --------------------------------------------------------------------------------------- |
| `VOIE-PF1` | `goodlogin`              | `BW` (bi-weekly)    | [PDF](https://truv.com/wp-content/uploads/2025/11/report-goodlogin.pdf?v=1)             |
| `VOIE-PF2` | `goodlogin.weekly`       | `W` (weekly)        | [PDF](https://truv.com/wp-content/uploads/2025/11/report-goodlogin.weekly.pdf?v=1)      |
| `VOIE-PF3` | `goodlogin.semimonthly`  | `SM` (semi-monthly) | [PDF](https://truv.com/wp-content/uploads/2025/11/report-goodlogin.semimonthly.pdf?v=1) |
| `VOIE-PF4` | `goodlogin.monthly`      | `M` (monthly)       | [PDF](https://truv.com/wp-content/uploads/2025/11/report-goodlogin.monthly.pdf?v=1)     |
| `VOIE-PF5` | `goodlogin.semiannually` | `SA` (semi-annual)  | —                                                                                       |

Use these to validate your handling of `pay_frequency` (`W`, `BW`, `SM`, `M`, `SA`) and to confirm your annualization logic across cadences.

<Accordion title="Sample responses">
  <Tabs>
    <Tab title="Weekly (W)">
      Same report shape as the quick-start response above; `pay_frequency` is `W` and statements arrive weekly.

      ```json theme={null}
      {
        "links": [
          {
            "provider_name": "Truv payroll provider",
            "employments": [
              {
                "company": "Acme",
                "job_title": "PR associate",
                "job_type": "F",
                "is_active": true,
                "start_date": "2023-01-01",
                "end_date": null,
                "pay_frequency": "W",
                "income": "56104.88",
                "income_unit": "YEARLY",
                "pay_rate": "1078.94",
                "statement_count": 108,
                "w2_count": 3,
                "missing_data_fields": [],
                "latest_statement": {
                  "pay_date": "2026-06-15",
                  "period_start": "2026-06-09",
                  "period_end": "2026-06-15",
                  "hours": "38.20",
                  "gross_pay": "1078.94",
                  "net_pay": "815.00",
                  "gross_pay_ytd": "25894.56",
                  "net_pay_ytd": "19560.00"
                }
              }
            ]
          }
        ]
      }
      ```
    </Tab>

    <Tab title="Semi-monthly (SM)">
      Same shape; `pay_frequency` is `SM` (twice a month).

      ```json theme={null}
      {
        "links": [
          {
            "provider_name": "Truv payroll provider",
            "employments": [
              {
                "company": "Acme",
                "job_title": "PR associate",
                "job_type": "F",
                "is_active": true,
                "start_date": "2023-01-01",
                "end_date": null,
                "pay_frequency": "SM",
                "income": "51789.36",
                "income_unit": "YEARLY",
                "pay_rate": "2157.89",
                "statement_count": 50,
                "w2_count": 3,
                "missing_data_fields": [],
                "latest_statement": {
                  "pay_date": "2026-06-15",
                  "period_start": "2026-06-01",
                  "period_end": "2026-06-15",
                  "hours": "76.40",
                  "gross_pay": "2157.89",
                  "net_pay": "1629.98",
                  "gross_pay_ytd": "23736.79",
                  "net_pay_ytd": "17929.78"
                }
              }
            ]
          }
        ]
      }
      ```
    </Tab>

    <Tab title="Monthly (M)">
      Same shape; `pay_frequency` is `M`.

      ```json theme={null}
      {
        "links": [
          {
            "provider_name": "Truv payroll provider",
            "employments": [
              {
                "company": "Acme",
                "job_title": "PR associate",
                "job_type": "F",
                "is_active": true,
                "start_date": "2023-01-01",
                "end_date": null,
                "pay_frequency": "M",
                "income": "51789.36",
                "income_unit": "YEARLY",
                "pay_rate": "4315.78",
                "statement_count": 26,
                "w2_count": 3,
                "missing_data_fields": [],
                "latest_statement": {
                  "pay_date": "2026-05-31",
                  "period_start": "2026-05-01",
                  "period_end": "2026-05-31",
                  "hours": "152.81",
                  "gross_pay": "4315.78",
                  "net_pay": "3259.96",
                  "gross_pay_ytd": "21578.90",
                  "net_pay_ytd": "16299.80"
                }
              }
            ]
          }
        ]
      }
      ```
    </Tab>

    <Tab title="Semi-annual (SA)">
      Two payments per year. Each statement covers half a year. `income`, `income_unit`, and `pay_rate` come back `null` for this salaried profile. Read the cadence from `pay_frequency` and the statement period.

      ```json theme={null}
      {
        "links": [
          {
            "provider_name": "Truv payroll provider",
            "employments": [
              {
                "company": "Timberline Consulting",
                "job_title": null,
                "job_type": "F",
                "is_active": true,
                "start_date": "2025-06-30",
                "end_date": null,
                "pay_frequency": "SA",
                "income": null,
                "income_unit": null,
                "pay_rate": null,
                "statement_count": 3,
                "w2_count": 0,
                "missing_data_fields": [],
                "latest_statement": {
                  "pay_date": "2026-06-30",
                  "period_start": "2026-01-01",
                  "period_end": "2026-06-30",
                  "hours": null,
                  "gross_pay": "36000.00",
                  "net_pay": "36000.00",
                  "basis_of_pay": "S"
                }
              }
            ]
          }
        ]
      }
      ```
    </Tab>
  </Tabs>
</Accordion>

### Earnings composition

These credentials connect through the **Truv Payroll Provider**. They return paychecks where pay includes more than base wages. Use them to check how your decisioning reads the `bonus` and `commission` statement fields, their `*_ytd` totals, and the `earnings[].category` on each line.

| Scenario  | Username                     | What it returns                                                                                                   |
| --------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `VOIE-C1` | `goodlogin.bonus`            | Wages plus periodic **bonus** payments (`bonus`, `bonus_ytd`, `earnings[].category: "bonus"`)                     |
| `VOIE-C2` | `goodlogin.bonus.commission` | Wages plus **bonus and commission** on the same statements                                                        |
| `VOIE-C3` | `goodlogin.commission`       | **Commission only** earner. `pay_frequency` is `C` and gross pay equals commission, with no hourly or salary base |

<Accordion title="Sample responses">
  <Tabs>
    <Tab title="Bonus (C1)">
      Bonus payments land on some statements and not others. `bonus` is `null` on a statement with no bonus. `bonus_ytd` adds up across the year, and `annual_income_summary[].bonus` carries the yearly total.

      ```json theme={null}
      {
        "links": [
          {
            "provider_name": "Truv payroll provider",
            "employments": [
              {
                "company": "Cascade Logistics",
                "job_title": null,
                "job_type": "F",
                "is_active": true,
                "start_date": "2026-03-13",
                "end_date": null,
                "pay_frequency": "BW",
                "income": "35.00",
                "income_unit": "HOURLY",
                "pay_rate": null,
                "statement_count": 6,
                "w2_count": 0,
                "missing_data_fields": [],
                "latest_statement": {
                  "pay_date": "2026-05-22",
                  "period_start": "2026-05-08",
                  "period_end": "2026-05-22",
                  "hours": "80.00",
                  "gross_pay": "3300.00",
                  "net_pay": "3300.00",
                  "regular": "2800.00",
                  "bonus": "500.00",
                  "bonus_ytd": "1500.00",
                  "gross_pay_ytd": "18300.00"
                }
              }
            ]
          }
        ]
      }
      ```
    </Tab>

    <Tab title="Bonus + commission (C2)">
      Both `bonus` and `commission` appear on the same statement, on top of `regular` wages.

      ```json theme={null}
      {
        "links": [
          {
            "provider_name": "Truv payroll provider",
            "employments": [
              {
                "company": "Northwest Sales",
                "job_title": null,
                "job_type": "F",
                "is_active": true,
                "start_date": "2026-02-28",
                "end_date": null,
                "pay_frequency": "SM",
                "income": "31.25",
                "income_unit": "HOURLY",
                "pay_rate": null,
                "statement_count": 6,
                "w2_count": 0,
                "missing_data_fields": [],
                "latest_statement": {
                  "pay_date": "2026-05-15",
                  "period_start": "2026-05-01",
                  "period_end": "2026-05-15",
                  "hours": "80.00",
                  "gross_pay": "4000.00",
                  "net_pay": "4000.00",
                  "bonus": "1000.00",
                  "commission": "500.00"
                }
              }
            ]
          }
        ]
      }
      ```
    </Tab>

    <Tab title="Commission-only (C3)">
      A commission only earner. `pay_frequency` is `C`, `gross_pay` equals `commission`, and `income`, `income_unit`, and `hours` are `null`. There is no hourly or salary base.

      ```json theme={null}
      {
        "links": [
          {
            "provider_name": "Truv payroll provider",
            "employments": [
              {
                "company": "Pacific Coast Real Estate",
                "job_title": null,
                "job_type": "F",
                "is_active": true,
                "start_date": "2025-11-01",
                "end_date": null,
                "pay_frequency": "C",
                "income": null,
                "income_unit": null,
                "pay_rate": null,
                "statement_count": 6,
                "w2_count": 0,
                "missing_data_fields": [],
                "latest_statement": {
                  "pay_date": "2026-05-01",
                  "period_start": "2026-04-01",
                  "period_end": "2026-04-30",
                  "hours": null,
                  "gross_pay": "5200.00",
                  "net_pay": "5200.00",
                  "commission": "5200.00",
                  "basis_of_pay": "S"
                }
              }
            ]
          }
        ]
      }
      ```
    </Tab>
  </Tabs>
</Accordion>

### Employment type

| Scenario  | Username                | SSN           | What it returns                                                                                                                                                                                                 |
| --------- | ----------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `VOIE-E1` | `fulltime`              | `2211`        | Full-time salesperson with regular salaried paystubs                                                                                                                                                            |
| `VOIE-E2` | `hourly.part-time`      | `666382548`   | Hourly part-time worker with variable hours per stub                                                                                                                                                            |
| `VOIE-E3` | `partial.data`          | `2222`        | Payroll connection succeeds but returns sparse employment metadata — `job_title`, `job_type`, and `original_hire_date` come back `null`. Use to validate your handling of null fields on the employment object. |
| `VOIE-E4` | `goodlogin.min-data`    | `991-91-9991` | Minimal information returned (no W-2s, sparse statements)                                                                                                                                                       |
| `VOIE-E5` | `multiple.employments`  | —             | Multiple different employers attached to one applicant                                                                                                                                                          |
| `VOIE-E6` | `multiple.employments2` | `7740`        | Multiple same-employer positions (e.g. transferred roles)                                                                                                                                                       |
| `VOIE-E7` | `nonactive`             | `1890`        | Employment with an `end_date` set and `is_active: false`                                                                                                                                                        |

<Accordion title="Sample responses">
  <Tabs>
    <Tab title="Full-time (E1)">
      Salaried full-time employee with regular paystubs.

      ```json theme={null}
      {
        "links": [
          {
            "provider_name": "Truv payroll provider",
            "employments": [
              {
                "company": "Real Estate Business",
                "job_title": "Sales Associate",
                "job_type": "F",
                "is_active": true,
                "start_date": "2023-03-12",
                "end_date": null,
                "pay_frequency": "SM",
                "income": "88663.20",
                "income_unit": "YEARLY",
                "pay_rate": "2812.50",
                "statement_count": 50,
                "w2_count": 3,
                "missing_data_fields": [],
                "latest_statement": {
                  "pay_date": "2026-06-15",
                  "period_start": "2026-06-01",
                  "period_end": "2026-06-15",
                  "hours": "86.67",
                  "gross_pay": "3694.30",
                  "net_pay": "3166.39",
                  "gross_pay_ytd": "40637.30",
                  "net_pay_ytd": "34830.29"
                }
              }
            ]
          }
        ]
      }
      ```
    </Tab>

    <Tab title="Part-time (E2)">
      Hourly worker; note variable `hours` across statements.

      ```json theme={null}
      {
        "links": [
          {
            "provider_name": "Truv payroll provider",
            "employments": [
              {
                "company": "Acme",
                "job_title": "Customer Service Representative",
                "job_type": "P",
                "is_active": true,
                "start_date": "2023-01-01",
                "end_date": null,
                "pay_frequency": "M",
                "income": "17.00",
                "income_unit": "HOURLY",
                "pay_rate": null,
                "statement_count": 26,
                "w2_count": 3,
                "missing_data_fields": [],
                "latest_statement": {
                  "pay_date": "2026-05-31",
                  "period_start": "2026-05-01",
                  "period_end": "2026-05-31",
                  "hours": "145.00",
                  "gross_pay": "2465.00",
                  "net_pay": "2365.00",
                  "gross_pay_ytd": "12325.00",
                  "net_pay_ytd": "11825.00"
                }
              }
            ]
          }
        ]
      }
      ```
    </Tab>

    <Tab title="Partial data (E3)">
      A connection that succeeds but returns sparse employment metadata — here `job_title`, `job_type`, and `original_hire_date` come back `null`. (`missing_data_fields` is empty; the gaps surface as null fields on the employment object.)

      ```json theme={null}
      {
        "links": [
          {
            "provider_name": "Truv payroll provider",
            "employments": [
              {
                "company": "INITECH",
                "job_title": null,
                "job_type": null,
                "is_active": true,
                "start_date": "2023-12-15",
                "end_date": null,
                "pay_frequency": "SM",
                "income": "199999.92",
                "income_unit": "YEARLY",
                "pay_rate": "8333.33",
                "statement_count": 58,
                "w2_count": 3,
                "missing_data_fields": [],
                "latest_statement": {
                  "pay_date": "2026-06-15",
                  "period_start": "2026-06-01",
                  "period_end": "2026-06-15",
                  "hours": null,
                  "gross_pay": "8333.33",
                  "net_pay": "7805.42",
                  "gross_pay_ytd": "91666.63",
                  "net_pay_ytd": "85859.62"
                }
              }
            ]
          }
        ]
      }
      ```
    </Tab>

    <Tab title="Min data (E4)">
      Minimal information: employment summary only, with no statements and no W-2s.

      ```json theme={null}
      {
        "links": [
          {
            "provider_name": "Truv payroll provider",
            "employments": [
              {
                "company": "Acme",
                "job_title": "PR associate",
                "job_type": "F",
                "is_active": true,
                "start_date": null,
                "end_date": null,
                "pay_frequency": "BW",
                "income": "56269.25",
                "income_unit": "YEARLY",
                "pay_rate": "2153.60",
                "statement_count": 0,
                "w2_count": 0,
                "missing_data_fields": [],
                "latest_statement": null
              }
            ]
          }
        ]
      }
      ```
    </Tab>

    <Tab title="Multi-employer (E5)">
      Two different employers attached to one applicant under a single link — one active, one ended.

      ```json theme={null}
      {
        "links": [
          {
            "provider_name": "Truv payroll provider",
            "employments": [
              {
                "company": "PAPER COMPANY",
                "job_title": "Senior Engineer",
                "job_type": "F",
                "is_active": true,
                "start_date": "2023-10-18",
                "end_date": null,
                "pay_frequency": "BW",
                "income": "75.96",
                "income_unit": "HOURLY",
                "pay_rate": null,
                "statement_count": 54,
                "w2_count": 3,
                "missing_data_fields": [],
                "latest_statement": {
                  "pay_date": "2026-06-08",
                  "period_start": "2026-05-26",
                  "period_end": "2026-06-08",
                  "hours": "80.00",
                  "gross_pay": "7807.69",
                  "net_pay": "7807.69",
                  "gross_pay_ytd": "93692.28",
                  "net_pay_ytd": "93692.28"
                }
              },
              {
                "company": "TEXTILES",
                "job_title": "Engineer",
                "job_type": "F",
                "is_active": false,
                "start_date": "2022-06-15",
                "end_date": "2023-09-30",
                "pay_frequency": "BW",
                "income": "149430.58",
                "income_unit": "YEARLY",
                "pay_rate": "5747.33",
                "statement_count": 34,
                "w2_count": 2,
                "missing_data_fields": [],
                "latest_statement": {
                  "pay_date": "2023-09-30",
                  "period_start": "2023-09-19",
                  "period_end": "2023-09-30",
                  "hours": "96.00",
                  "gross_pay": "5819.42",
                  "net_pay": "5819.42",
                  "gross_pay_ytd": "116388.40",
                  "net_pay_ytd": "116388.40"
                }
              }
            ]
          }
        ]
      }
      ```
    </Tab>

    <Tab title="Same-employer (E6)">
      Multiple positions at the same employer (e.g. transferred roles).

      ```json theme={null}
      {
        "links": [
          {
            "provider_name": "Truv payroll provider",
            "employments": [
              {
                "company": "Moment Developments",
                "job_title": "Director, Accounts",
                "job_type": "F",
                "is_active": false,
                "start_date": "2023-07-15",
                "end_date": "2024-01-15",
                "pay_frequency": "SM",
                "income": "60000.00",
                "income_unit": "YEARLY",
                "pay_rate": "2500.00",
                "statement_count": 12,
                "w2_count": 2,
                "missing_data_fields": [],
                "latest_statement": {
                  "pay_date": "2024-01-15",
                  "period_start": "2024-01-01",
                  "period_end": "2024-01-15",
                  "hours": null,
                  "gross_pay": "2500.00",
                  "net_pay": "2500.00",
                  "gross_pay_ytd": "2500.00",
                  "net_pay_ytd": "2500.00"
                }
              },
              {
                "company": "Moment Developments",
                "job_title": "Senior Account Manager",
                "job_type": null,
                "is_active": false,
                "start_date": "2022-11-01",
                "end_date": "2023-05-01",
                "pay_frequency": "SM",
                "income": "54000.00",
                "income_unit": "YEARLY",
                "pay_rate": "2250.00",
                "statement_count": 13,
                "w2_count": 2,
                "missing_data_fields": [],
                "latest_statement": {
                  "pay_date": "2023-05-01",
                  "period_start": null,
                  "period_end": null,
                  "hours": null,
                  "gross_pay": "2250.00",
                  "net_pay": "2250.00",
                  "gross_pay_ytd": "20250.00",
                  "net_pay_ytd": "20250.00"
                }
              }
            ]
          }
        ]
      }
      ```
    </Tab>

    <Tab title="Ended (E7)">
      Employment with `is_active: false` and an `end_date` set.

      ```json theme={null}
      {
        "links": [
          {
            "provider_name": "Truv payroll provider",
            "employments": [
              {
                "company": "Rhode Island Consultants",
                "job_title": "Admin Services Manager",
                "job_type": "F",
                "is_active": false,
                "start_date": "2023-02-25",
                "end_date": "2024-06-15",
                "pay_frequency": "SM",
                "income": "72450.00",
                "income_unit": "YEARLY",
                "pay_rate": "3169.69",
                "statement_count": 48,
                "w2_count": 2,
                "missing_data_fields": [],
                "latest_statement": {
                  "pay_date": "2024-06-15",
                  "period_start": null,
                  "period_end": null,
                  "hours": "86.66",
                  "gross_pay": "6797.56",
                  "net_pay": "6797.56",
                  "gross_pay_ytd": "81570.72",
                  "net_pay_ytd": "81570.72"
                }
              }
            ]
          }
        ]
      }
      ```
    </Tab>
  </Tabs>
</Accordion>

### Tenure

Use these credentials when your decisioning depends on the borrower's tenure with their current employer — for example, PLL eligibility rules that require a minimum employment length, or income-stability decisioning that looks at months at current employer.

| Scenario  | Username        | Returns                                                                                                                         |
| --------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `VOIE-T1` | `goodlogin.tn1` | Active employment with **less than 12 months tenure** at the current employer                                                   |
| `VOIE-T2` | `goodlogin.tn2` | Active employment with **less than 12 months tenure** at the current employer (alternate profile for multi-applicant test sets) |

Both credentials produce the same response shape as `goodlogin` / `goodpassword` — the only difference is that `start_date` is set to a date inside the last 12 months. Use one or the other (or both) when your QA matrix has multiple sub-12-month scenarios that need distinct credentials.

<Accordion title="Sample responses">
  <Tabs>
    <Tab title="Under 12 mo (T1)">
      `start_date` falls inside the last 12 months. Use to confirm tenure-based eligibility logic.

      ```json theme={null}
      {
        "links": [
          {
            "provider_name": "Truv payroll provider",
            "employments": [
              {
                "company": "Acme",
                "job_title": "PR associate",
                "job_type": null,
                "is_active": true,
                "start_date": "2026-02-02",
                "end_date": null,
                "pay_frequency": "SM",
                "income": "51789.36",
                "income_unit": "YEARLY",
                "pay_rate": "2157.89",
                "statement_count": 9,
                "w2_count": 0,
                "missing_data_fields": [],
                "latest_statement": {
                  "pay_date": "2026-06-15",
                  "period_start": "2026-06-01",
                  "period_end": "2026-06-15",
                  "hours": "76.40",
                  "gross_pay": "2157.89",
                  "net_pay": "1629.98",
                  "gross_pay_ytd": "19421.01",
                  "net_pay_ytd": "14669.82"
                }
              }
            ]
          }
        ]
      }
      ```
    </Tab>

    <Tab title="Under 12 mo alt (T2)">
      Alternate sub-12-month profile; here the connection returns the employment summary with no statements.

      ```json theme={null}
      {
        "links": [
          {
            "provider_name": "Truv payroll provider",
            "employments": [
              {
                "company": "Acme",
                "job_title": "PR associate",
                "job_type": null,
                "is_active": false,
                "start_date": "2026-02-02",
                "end_date": "2026-02-15",
                "pay_frequency": "SM",
                "income": "51789.36",
                "income_unit": "YEARLY",
                "pay_rate": "2157.89",
                "statement_count": 0,
                "w2_count": 0,
                "missing_data_fields": [],
                "latest_statement": null
              }
            ]
          }
        ]
      }
      ```
    </Tab>
  </Tabs>
</Accordion>

### Benefits and special cases

Both scenarios return SSN `9988`.

| Scenario  | Username       | What it returns                                 |
| --------- | -------------- | ----------------------------------------------- |
| `VOIE-B1` | `military`     | Military MyPay data via the DFAS myPay provider |
| `VOIE-B2` | `unemployment` | Weekly unemployment benefits                    |

**Retirement, disability, and Veterans benefits:** Search for the benefit type in Truv Bridge (for example, "SSA" for Social Security or "VA Benefits"), pick a login method, and use `goodlogin` / `goodpassword`. Retirement and disability return a Social Security Administration Benefits Verification Letter. Veterans Benefits return a VA Benefit Summary Letter via the Veterans Affairs provider, or a retirement paystub via the DFAS myPay provider.

<Accordion title="Sample responses">
  <Tabs>
    <Tab title="Military (B1)">
      Military service member pay record.

      ```json theme={null}
      {
        "links": [
          {
            "provider_name": "Truv payroll provider",
            "employments": [
              {
                "company": "United States Air Force",
                "job_title": "AF O4",
                "job_type": "F",
                "is_active": true,
                "start_date": "2023-03-16",
                "end_date": null,
                "pay_frequency": "M",
                "income": "141015.71",
                "income_unit": "YEARLY",
                "pay_rate": "7192.20",
                "statement_count": 26,
                "w2_count": 3,
                "missing_data_fields": [],
                "latest_statement": {
                  "pay_date": "2026-05-31",
                  "period_start": "2026-05-01",
                  "period_end": "2026-05-31",
                  "hours": null,
                  "gross_pay": "16332.67",
                  "net_pay": "16332.67",
                  "gross_pay_ytd": "81663.35",
                  "net_pay_ytd": "81663.35"
                }
              }
            ]
          }
        ]
      }
      ```
    </Tab>

    <Tab title="Unemployment (B2)">
      Weekly unemployment benefit income.

      ```json theme={null}
      {
        "links": [
          {
            "provider_name": "Truv payroll provider",
            "employments": [
              {
                "company": "Illinois Department of Employment Security",
                "job_title": "Unemployed",
                "job_type": null,
                "is_active": true,
                "start_date": "2023-08-14",
                "end_date": null,
                "pay_frequency": "W",
                "income": "450.00",
                "income_unit": "WEEKLY",
                "pay_rate": null,
                "statement_count": 108,
                "w2_count": 3,
                "missing_data_fields": [],
                "latest_statement": {
                  "pay_date": "2026-06-15",
                  "period_start": "2026-06-09",
                  "period_end": "2026-06-15",
                  "hours": null,
                  "gross_pay": "750.00",
                  "net_pay": "750.00",
                  "gross_pay_ytd": "18000.00",
                  "net_pay_ytd": "18000.00"
                }
              }
            ]
          }
        ]
      }
      ```
    </Tab>
  </Tabs>
</Accordion>

### Gig and shift workers

| Scenario  | Username          | What it returns                                                                                                                      |
| --------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `VOIE-G1` | `goodlogin.shift` | Gig worker with earned wage access — use for testing gig-payroll VOIE flows where hours and earnings cadence differ from W-2 payroll |

<Note>
  Gig provider connections return different field coverage than W-2 payroll connections. DoorDash returns gross pay and hours but no net pay or deductions; Uber returns gross and net pay but no hours. Validate your decisioning logic against partial-coverage responses, not against the assumption that every field will be populated.
</Note>

<Accordion title="Sample response — gig / earned wage access">
  Gig worker with earned wage access; field coverage differs from W-2 payroll.

  ```json theme={null}
  {
    "links": [
      {
        "provider_name": "Truv payroll provider",
        "employments": [
          {
            "company": "DoorDash",
            "job_title": "Gig Worker",
            "job_type": null,
            "is_active": true,
            "start_date": "2026-04-06",
            "end_date": null,
            "pay_frequency": "W",
            "income": null,
            "income_unit": null,
            "pay_rate": null,
            "statement_count": 8,
            "w2_count": 0,
            "missing_data_fields": [],
            "latest_statement": {
              "pay_date": "2026-05-24",
              "period_start": "2026-05-17",
              "period_end": "2026-05-23",
              "hours": "31.00",
              "gross_pay": "618.50",
              "net_pay": null,
              "gross_pay_ytd": null,
              "net_pay_ytd": null
            }
          }
        ]
      }
    ]
  }
  ```
</Accordion>

### Income changes and outliers

These credentials connect through the **Truv Payroll Provider**. Each returns a statement history with a clear value pattern. One trends up, one trends down, one holds high values, and one carries a single spike. Use them to check how your trend, averaging, and outlier logic reads movement across statements instead of a single period.

| Scenario  | Username          | What it returns                                                                |
| --------- | ----------------- | ------------------------------------------------------------------------------ |
| `VOIE-D1` | `pay.raise`       | Ongoing **pay increase**. `gross_pay` steps up across consecutive statements   |
| `VOIE-D2` | `pay.reduction`   | Ongoing **pay decrease**. `gross_pay` steps down across consecutive statements |
| `VOIE-D3` | `extreme.income`  | **Very high values** for `gross_pay`, `hours`, and bonus on every statement    |
| `VOIE-D4` | `outlier.paystub` | A steady history with **one outlier statement** far larger than the rest       |

<Note>
  `pay.raise` and `pay.reduction` only show their pattern across the full statement set. Read the `statements[]` array from oldest to newest, not just `latest_statement`. `outlier.paystub` returns normal statements with one spike. Check each statement value, not the average alone.
</Note>

<Accordion title="Sample responses">
  <Tabs>
    <Tab title="Pay raise (D1)">
      `gross_pay` rises on every statement, so the most recent statement is the highest. The values in the sample below show the trend.

      ```json theme={null}
      {
        "links": [
          {
            "provider_name": "Truv payroll provider",
            "employments": [
              {
                "company": "Emerald City Tech",
                "job_title": null,
                "job_type": "F",
                "is_active": true,
                "start_date": "2026-03-13",
                "end_date": null,
                "pay_frequency": "BW",
                "income": null,
                "income_unit": null,
                "pay_rate": null,
                "statement_count": 6,
                "w2_count": 0,
                "missing_data_fields": [],
                "latest_statement": {
                  "pay_date": "2026-05-22",
                  "period_start": "2026-05-08",
                  "period_end": "2026-05-22",
                  "hours": "80.00",
                  "gross_pay": "3500.00",
                  "net_pay": "3500.00"
                }
              }
            ]
          }
        ]
      }
      ```
    </Tab>

    <Tab title="Pay reduction (D2)">
      `gross_pay` falls on every statement, so the most recent statement is the lowest. The values in the sample below show the trend.

      ```json theme={null}
      {
        "links": [
          {
            "provider_name": "Truv payroll provider",
            "employments": [
              {
                "company": "Coastal Retail Partners",
                "job_title": null,
                "job_type": "F",
                "is_active": true,
                "start_date": "2026-03-13",
                "end_date": null,
                "pay_frequency": "BW",
                "income": null,
                "income_unit": null,
                "pay_rate": null,
                "statement_count": 6,
                "w2_count": 0,
                "missing_data_fields": [],
                "latest_statement": {
                  "pay_date": "2026-05-22",
                  "period_start": "2026-05-08",
                  "period_end": "2026-05-22",
                  "hours": "80.00",
                  "gross_pay": "2000.00",
                  "net_pay": "2000.00"
                }
              }
            ]
          }
        ]
      }
      ```
    </Tab>

    <Tab title="Extreme income (D3)">
      Every statement carries high values for `gross_pay`, `hours`, and bonus. Use this to confirm your fields and decisioning hold at the top of the value range.

      ```json theme={null}
      {
        "links": [
          {
            "provider_name": "Truv payroll provider",
            "employments": [
              {
                "company": "Apex Heavy Industries",
                "job_title": null,
                "job_type": "F",
                "is_active": true,
                "start_date": "2026-03-13",
                "end_date": null,
                "pay_frequency": "BW",
                "income": "100.00",
                "income_unit": "HOURLY",
                "pay_rate": null,
                "statement_count": 6,
                "w2_count": 0,
                "missing_data_fields": [],
                "latest_statement": {
                  "pay_date": "2026-05-22",
                  "period_start": "2026-05-08",
                  "period_end": "2026-05-22",
                  "hours": "320.00",
                  "gross_pay": "48000.00",
                  "net_pay": "48000.00",
                  "bonus": "8000.00"
                }
              }
            ]
          }
        ]
      }
      ```
    </Tab>

    <Tab title="Outlier paystub (D4)">
      Most statements hold a steady value. One statement spikes far above the rest. `latest_statement` shows the normal value, so look through the `statements[]` array to find the spike.

      ```json theme={null}
      {
        "links": [
          {
            "provider_name": "Truv payroll provider",
            "employments": [
              {
                "company": "Valley Manufacturing",
                "job_title": null,
                "job_type": "F",
                "is_active": true,
                "start_date": "2026-03-13",
                "end_date": null,
                "pay_frequency": "BW",
                "income": "31.25",
                "income_unit": "HOURLY",
                "pay_rate": null,
                "statement_count": 6,
                "w2_count": 0,
                "missing_data_fields": [],
                "latest_statement": {
                  "pay_date": "2026-05-22",
                  "period_start": "2026-05-08",
                  "period_end": "2026-05-22",
                  "hours": "80.00",
                  "gross_pay": "2500.00",
                  "net_pay": "2500.00"
                }
              }
            ]
          }
        ]
      }
      ```
    </Tab>
  </Tabs>
</Accordion>

***

## Error scenarios

VOIE shares the universal authentication-error credentials. Use these to test your error handling and retry UX.

| Scenario   | Credential                      | Task status                | Use it to test                                  |
| ---------- | ------------------------------- | -------------------------- | ----------------------------------------------- |
| `VOIE-X1`  | `goodlogin` / `no_data`         | `no_data`                  | Successful login, provider returns zero records |
| `VOIE-X2`  | `goodlogin` / `mfa`             | `mfa` → `done`             | MFA flow — enter code `12345`                   |
| `VOIE-X3`  | `goodlogin` / `mfa_captcha`     | `mfa` → `done`             | MFA captcha — enter answer `9M4BP`              |
| `VOIE-X4`  | `goodlogin` / `mfa_select`      | `mfa` → `done`             | MFA verification-method selection               |
| `VOIE-X5`  | `goodlogin` / `longcheck`       | `parse` → `done` (delayed) | \~30 second processing delay                    |
| `VOIE-X6`  | `error.user` / `login_error`    | `login_error`              | Incorrect credentials                           |
| `VOIE-X7`  | `error.user` / `mfa_error`      | `mfa_error`                | MFA verification failed                         |
| `VOIE-X8`  | `error.user` / `account_locked` | `account_locked`           | Account locked by provider                      |
| `VOIE-X9`  | `error.user` / `unavailable`    | `unavailable`              | Provider under maintenance                      |
| `VOIE-X10` | `error.user` / `error`          | `error`                    | Generic login error                             |
| `VOIE-X11` | `error.user` / `config_error`   | `config_error`             | Organization ID configuration failure           |

See [Task lifecycle](/api-reference/tasks/lifecycle) for the full status reference and the meaning of each error state.

***

## Fraud and suspicious documents

Document-uploaded VOIE uses filename-driven triggers in sandbox to simulate fraud detection. The full filename → task status + `error_message` map lives on the [Test Documents](/developers/testing/test-documents#suspicious-document-detection) page.

For interpreting `is_suspicious` on the report, fraud-tier behavior, and recommended manual-review workflows, see [Fraud Detection & Manual Review](/developers/fraud-and-manual-review).

***

## Webhook events

When testing VOIE end to end, your webhook endpoint receives one `task-status-updated` event per status transition plus one `order-status-updated` event per connection when the user finishes the order.

<Accordion title="Sample webhook payloads">
  Per-task event (one per employer connection):

  ```json theme={null}
  {
    "webhook_id": "609a82aab21e4d9ba2569f35e9e8f26a",
    "event_type": "task-status-updated",
    "updated_at": "2026-04-26T13:02:20.369267+00:00",
    "task_id": "67f2924530564282bbaf6d27655e94a4",
    "link_id": "64f8e374949c4b769706028022626bf1",
    "product": "income",
    "tracking_info": "your-applicant-id",
    "status": "done"
  }
  ```

  Order event (one per connection — fires for each unique `link_id` when the user closes Bridge via **Finish and Share**, so an order with more than one connection emits multiple `order-status-updated` events):

  ```json theme={null}
  {
    "webhook_id": "698e974cb30646189862229199f7ed1a",
    "event_type": "order-status-updated",
    "event_created_at": "2026-04-26T13:02:35.172897+00:00",
    "product": "income",
    "link_id": "64f8e374949c4b769706028022626bf1",
    "order_id": "45771e47434341039c61ccb28caeb9d8",
    "order_number": "your-loan-number",
    "status": "completed"
  }
  ```
</Accordion>

For the difference between these two events and why `tracking_info` is the reliable mapping key, see the [Orders object](/api-reference/orders/object#order-lifecycle-and-webhooks) reference.

***

## Mortgage VOIE (GSE testing)

For Fannie Mae Day 1 Certainty and Freddie Mac AIM testing — with borrower test accounts that map to specific GSE submission outcomes — see [GSE Testing](/developers/testing/gse-testing) and the credential tables on the [Fannie Mae D1C](/mortgage/d1c-aim/fannie-mae) and [Freddie Mac AIM](/mortgage/d1c-aim/freddie-mac) pages.

***

## Next steps

<CardGroup cols={2}>
  <Card title="Bridge Widget Guide" icon="book-open" href="/developers/integration/bridge-widget/overview">
    Mint a bridge token and embed the widget
  </Card>

  <Card title="Embedded Orders" icon="receipt" href="/developers/integration/embedded-orders/overview">
    Order-driven VOIE with auto-generated reports
  </Card>

  <Card title="GSE Testing" icon="building-columns" href="/developers/testing/gse-testing">
    Fannie Mae D1C and Freddie Mac AIM workflows
  </Card>

  <Card title="Report Object" icon="file-lines" href="/api-reference/user-income-and-employment-reports/object">
    JSON schema for the VOIE report
  </Card>
</CardGroup>
