# List Verification

List verification is the most common ApexVerify workflow. You upload a `.txt` file of emails or phone numbers, configure parameters, trigger parsing, and launch — the platform processes every record and returns detailed per-row results.

***

## Batch vs. Unit verification

<CardGroup cols={2}>
  <Card title="Batch verification" icon="fa-regular fa-layer-group">
    **Use for lists of 10+ records.** Upload a file, configure once, and let the API process everything asynchronously. Results are exported as XLSX or JSON when complete.
  </Card>

  <Card title="Unit verification" icon="fa-regular fa-bolt">
    **Use for single records in real time.** Submit one email or phone via `POST /v1/unit` and get a result in under 5 seconds. Ideal for live form validation.
  </Card>
</CardGroup>

This guide focuses on **batch verification**. For unit verification, see the [API Reference](/api-reference/authentication).

***

## Step 1: Prepare your list

<Steps>
  <Step title="Export your contacts">
    Export your contact list from your CRM, marketing platform, or database as a plain text file.
  </Step>

  <Step title="Format the file">
    The file must be UTF-8 encoded with **one entry per line** — no headers, no commas, no quotes.

    <Tabs>
      <Tab title="Email format">
        ```
        alice@example.com
        bob@company.org
        carol@domain.net
        ```

        Email addresses with obvious syntax errors will be counted as `wrong_email` during parsing.
      </Tab>

      <Tab title="Phone format (E.164)">
        ```
        +6591234567
        +447911123456
        +12125551234
        ```

        Phone numbers should include the country dialing code in E.164 format. Numbers without a country code will be flagged as `wrong_phone` during parsing.
      </Tab>
    </Tabs>
  </Step>

  <Step title="Pre-clean if needed">
    Remove obviously invalid rows before uploading to save credits. The platform will also deduplicate and strip malformed entries automatically if `remove_duplicate` and `remove_wrong_email_format` / `remove_wrong_phone_format` are enabled (all default to `true`).
  </Step>
</Steps>

***

## Step 2: Upload, configure, and parse

Follow the [API Verification Workflow](/tutorials/api-workflow) guide for the full upload → PUT → PATCH flow. In summary:

1. **Upload** — `POST /v1/batch` with your `.txt` file → get `batch_uuid`
2. **Configure** — `PUT /v1/batch/{uuid}` with `type` and `target_country` (required)
3. **Parse** — `PATCH /v1/batch/{uuid}` to trigger content parsing
4. **Launch** — `POST /v1/batch/{uuid}` once status reaches `ready_for_verification`

***

## Understanding parsing results

After `PATCH`, the batch transitions through `parsing_content` and then into `ready_for_verification`. The parsing summary is returned in the `found_content` field of `GET /v1/batch/{uuid}/details`:

<Tabs>
  <Tab title="Email parsing">
    | Field             | Type    | Description                            |
    | :---------------- | :------ | :------------------------------------- |
    | `valid_email`     | integer | Rows that parsed as valid email format |
    | `wrong_email`     | integer | Rows rejected due to invalid format    |
    | `duplicate_email` | integer | Rows removed as duplicates             |

    Only `valid_email` rows are sent to verification. Credits are charged for this count only.
  </Tab>

  <Tab title="Phone parsing">
    | Field             | Type    | Description                            |
    | :---------------- | :------ | :------------------------------------- |
    | `valid_phone`     | integer | Rows that parsed as valid phone format |
    | `wrong_phone`     | integer | Rows rejected due to invalid format    |
    | `duplicate_phone` | integer | Rows removed as duplicates             |
  </Tab>
</Tabs>

***

## Reading verification results

After verification is complete, export results via `GET /v1/batch/{uuid}/export?format=json`. Each row is keyed by the original email or phone value.

<Tabs>
  <Tab title="Email result fields">
    | Field             | Type    | Description                                              |
    | :---------------- | :------ | :------------------------------------------------------- |
    | `email`           | string  | The original email address                               |
    | `valid`           | boolean | `true` if the email is deliverable                       |
    | `wrong`           | boolean | `true` if the email is invalid or undeliverable          |
    | `unknown`         | boolean | `true` if the result is inconclusive                     |
    | `quality`         | string  | `good` \| `bad` \| `risky` \| `unknown`                  |
    | `result`          | string  | `ok` \| `invalid` \| `error` \| `unknown`                |
    | `is_syntax_error` | boolean | `true` if the email fails basic syntax validation        |
    | `is_free`         | boolean | `true` if sent from a free provider (Gmail, Yahoo, etc.) |
    | `is_role`         | boolean | `true` if it's a role address (info@, support@, etc.)    |
    | `is_disposable`   | boolean | `true` if from a known disposable email service          |
    | `is_catch_all`    | boolean | `true` if the domain accepts all addresses               |
  </Tab>

  <Tab title="Phone result fields">
    | Field                 | Type         | Description                                                           |
    | :-------------------- | :----------- | :-------------------------------------------------------------------- |
    | `phone`               | string       | The original phone number                                             |
    | `valid`               | boolean      | `true` if the number is valid and reachable                           |
    | `wrong`               | boolean      | `true` if the number is invalid                                       |
    | `unknown`             | boolean      | `true` if the result is inconclusive                                  |
    | `type`                | string       | Number type: `mobile`, `landline`, `voip`, `toll_free`, etc.          |
    | `status`              | string       | Live status: `live`, `dead`, `absent_subscriber`, `no_coverage`, etc. |
    | `is_syntax_error`     | boolean      | `true` if the number fails format validation                          |
    | `is_ported`           | boolean      | `true` if the number has been ported to another carrier               |
    | `current_network_mcc` | string\|null | Mobile Country Code of current network                                |
    | `current_network_mnc` | string\|null | Mobile Network Code of current network                                |
    | `operator`            | string\|null | Name of the current network operator                                  |
  </Tab>
</Tabs>

***

## Cost optimization tips

<Tip>
  **Use caching to reduce costs.** When `use_account_cache` and `use_global_cache` are enabled (both default to `true`), you get a **50% credit refund** for any record that hits the cache. For lists with significant overlap across campaigns, this can cut your verification costs substantially.

  Configure `max_account_cache_backoff` and `max_global_cache_backoff` (1–180 days) to control how far back the system looks. A lower backoff means fresher results; a higher backoff means more cache hits and lower cost.
</Tip>
