---
title: "Estimate a bulk job for free"
method: POST
path: "/jobs/estimate"
tags: ["jobs"]
---

# Estimate a bulk job for free

`POST /jobs/estimate`

## Request

- Base URL: `https://api.mobilevalidate.com/v1`
- URL: `https://api.mobilevalidate.com/v1/jobs/estimate`
- Auth: HTTP bearer

Validates and counts a list without storing it, calling a data source or charging: valid, invalid and duplicate rows, cached answers, the maximum possible cost (`max_cost`) and its `breakdown` per check and unit price. Checks in parts too small for the bulk price (WhatsApp: fewer than 132 numbers from one country in the job) are priced at the real-time price and listed with `reason: small_batch`. Bulk-only checks bill each country of the job at least `minimum_checks` × the bulk price when it returns an answer; countries with fewer numbers are listed with `reason: country_minimum` and their minimum is included in `max_cost`. Same body as POST /v1/jobs.

## Request body

- union: numbers and/or emails, together ≤ 50,000 (10 with the public sandbox key). Identifiers × applicable checks ≤ 100,000 per job (400 invalid_request, param checks); checked before anything is stored or reserved.
  - object
    - `numbers` string[], required
    - `emails` string[]
    - `checks` string[]: Service codes or aliases (e.g. whatsapp, telegram, viber, carrier, spam; e-mail: email, gmail, outlook, yahoo; full list: GET /v1/services). Order is kept; duplicates are ignored; whatsapp.registered + whatsapp.business collapse to whatsapp.business. Phone checks apply to numbers, e-mail checks (input_type email) to emails: sending e-mails without an e-mail check (or numbers without a phone check) → 400 invalid_request, param checks; a check whose input type has no identifiers → 400 invalid_request, param checks[i]. At most 20 checks (400 invalid_request, param checks).
    - `default_country` string
    - `max_age` integer
    - `max_cost` Money
      - `amount` string, required: Decimal USD amount.
      - `currency` 'USD', required
    - `webhook_endpoint_id` string
    - `metadata` object
  - object
    - `numbers` string[]
    - `emails` string[], required
    - `checks` string[]: Service codes or aliases (e.g. whatsapp, telegram, viber, carrier, spam; e-mail: email, gmail, outlook, yahoo; full list: GET /v1/services). Order is kept; duplicates are ignored; whatsapp.registered + whatsapp.business collapse to whatsapp.business. Phone checks apply to numbers, e-mail checks (input_type email) to emails: sending e-mails without an e-mail check (or numbers without a phone check) → 400 invalid_request, param checks; a check whose input type has no identifiers → 400 invalid_request, param checks[i]. At most 20 checks (400 invalid_request, param checks).
    - `default_country` string
    - `max_age` integer
    - `max_cost` Money
      - `amount` string, required: Decimal USD amount.
      - `currency` 'USD', required
    - `webhook_endpoint_id` string
    - `metadata` object

## Response `200`

The estimate.

- Estimate
  - `object` 'estimate'
  - `total` integer
  - `valid` integer
  - `invalid` integer
  - `duplicate` integer
  - `unsupported` integer
  - `suppressed` integer
  - `checks` string[]
  - `checks_total` integer: Σ rows × checks of the row's input type
  - `cached` integer: Checks (row × check) with a fresh answer in your account's cache now; free if still fresh when the job runs.
  - `billable_max` integer: Checks (row × check) that may be billed, worst case (includes `cached`).
  - `max_cost` Money
    - `amount` string, required: Decimal USD amount.
    - `currency` 'USD', required
  - `breakdown` PriceLine[]: Price lines per check: one `batch` line, plus a `realtime` line (reason `small_batch`) for checks in parts too small for the bulk price, or a `country_minimum` line for countries of a bulk-only check below its minimum per country. Σ max_cost of the lines = max_cost.
    - `check` string, required: Service code.
    - `price_mode` 'batch' | 'realtime', required: Which of the check's prices applies to these checks.
    - `reason` 'small_batch' | 'country_minimum' | 'null', nullable, required: Why a bulk-job line is priced differently. `small_batch`: the numbers of these countries form a part smaller than the bulk minimum (`batch_minimum` numbers per country for this check in one job), so they are checked the real-time way at the real-time price. Send at least `batch_minimum` numbers per country to get the bulk price. `country_minimum`: a bulk-only check (no real-time price); each country group (numbers of one country for this check in one job) is billed max(conclusive answers × unit_price, `minimum_charge`) when it returns at least one conclusive answer, and nothing otherwise. The line lists the countries with fewer than `minimum_checks` numbers; countries with more stay on the `batch` line (same rule, which only matters when fewer than `minimum_checks` of their numbers answer). New values may be added.
    - `checks` integer, required: Checks (row × check) at this unit price; valid rows only.
    - `unit_price` Money, required
      - `amount` string, required: Decimal USD amount.
      - `currency` 'USD', required
    - `max_cost` Money, required
      - `amount` string, required: Decimal USD amount.
      - `currency` 'USD', required
    - `countries` string[]: small_batch / country_minimum lines: ISO 3166-1 alpha-2 countries of the line.
    - `batch_minimum` integer: small_batch lines: numbers per country (for this check, in one job) needed for the bulk price.
    - `country_checks` object: country_minimum lines: numbers per country (for this check, in this job).
    - `minimum_checks` integer: country_minimum lines: checks billed at least per country that returns an answer (e.g. 500).
    - `minimum_charge` Money
      - `amount` string, required: Decimal USD amount.
      - `currency` 'USD', required
    - `note` string: country_minimum lines: plain-English explanation of the line.
  - `unsupported_checks` object[]: Checks that will not run for some countries of the list because the check does not cover them (see `unsupported_countries` in GET /v1/services). They are stored as `unsupported_country`, never reserved or billed, and are not part of `billable_max`. Empty when every check covers every country of the list.
    - `check` string, required: Service code.
    - `checks` integer, required: Checks (row × check) not run.
    - `countries` string[], required: ISO 3166-1 alpha-2 countries not covered.
    - `country_checks` object, required: Checks not run per country.

## Other responses

- `default`: Error. Branch on `error.code`; `suggestion` (optional) says how to fix the request. A known path called with the wrong method answers `404 not_found` with an `Allow` header listing the methods it accepts.

---

[API](https://skmtc.dev/mobilevalidate/apis/mobilevalidate-api.md) · [All operations](https://skmtc.dev/mobilevalidate/apis/mobilevalidate-api/llms.txt) · [OpenAPI document](https://skmtc.dev/mobilevalidate/apis/mobilevalidate-api/revisions/f2e56c2454ef?raw)
