# Migrate from DeBounce

This guide maps the DeBounce API v1 (single validation, bulk upload and status, and balance) to the Tomba [Email Verifier](/api/verifier#email-verifier) and [bulk verifier jobs](/bulks#email-verifier). DeBounce API behavior checked on 2026-09-30 against the [DeBounce API reference](https://developers.debounce.com/) and its [result codes](https://help.debounce.com/understanding-results/result-codes/).

## Before you begin

- Get your API key (`ta_…`) and secret (`ts_…`); see [Authentication](/authentication).
- Check what a verification costs in [Credit costs](/usage-and-quotas#credit-costs) and your plan's throttling in [Limits by plan](/rate-limits#limits-by-plan). Tomba throttles by requests per time window, not by concurrent calls.
- Set your HTTP client timeout to at least 180 seconds. Tomba returns the finished result in the response.

## Endpoint mapping

DeBounce uses two hosts, `api.debounce.io` and `bulk.debounce.io`. Tomba serves everything from `api.tomba.io`.

| DeBounce                                  | Tomba                                                                 |
| ----------------------------------------- | --------------------------------------------------------------------- |
| `GET https://api.debounce.io/v1/`         | [`GET /v1/email-verifier`](/api/verifier#email-verifier)              |
| `GET https://bulk.debounce.io/v1/upload/` | `POST /v1/bulk/verifier`, then `PUT /v1/bulk/verifier/{id}` to launch |
| `GET https://bulk.debounce.io/v1/status/` | `GET /v1/bulk/verifier/{id}/progress`                                 |
| The `download_link` from `/v1/status/`    | `GET /v1/bulk/verifier/{id}/download`                                 |
| `GET https://api.debounce.io/v1/balance/` | [`GET /v1/me`](/api/account#get-account)                              |

## Authentication changes

DeBounce reads the key from the `api` query parameter. Tomba needs two headers, `X-Tomba-Key` and `X-Tomba-Secret`, and doesn't accept credentials in the query string:

```bash
# DeBounce
curl "https://api.debounce.io/v1/?api=$DEBOUNCE_API_KEY&email=jane.doe@stripe.com"

# Tomba
curl "https://api.tomba.io/v1/email-verifier?email=jane.doe@stripe.com" \
  -H "X-Tomba-Key: $TOMBA_API_KEY" \
  -H "X-Tomba-Secret: $TOMBA_SECRET_KEY"
```

If you call DeBounce from browser code with a `public_` key, move the call to your server and keep the Tomba key and secret there ([Security](/security#api-credentials)). Tomba API keys expire; plan their rotation with [Key expiry](/authentication#key-expiry).

## Parameter mapping

| DeBounce          | Tomba                                      | Notes                                                                           |
| ----------------- | ------------------------------------------ | ------------------------------------------------------------------------------- |
| `api`             | `X-Tomba-Key` and `X-Tomba-Secret` headers |                                                                                 |
| `email`           | `email`                                    | Required. A malformed address returns `422 params_invalid` instead of a result. |
| `photo`, `append` | No equivalent                              |                                                                                 |
| `gsuite`          | No equivalent                              | Every Tomba response includes `data.email.accept_all`.                          |
| Upload `url`      | `file` or `list`                           | Tomba doesn't fetch lists from a URL. Upload the CSV as `file`, or send `list`. |
| `list_id`         | `{id}` in the path                         |                                                                                 |

## Response field mapping

DeBounce returns the result in a `debounce` object, with booleans and numbers as strings. Tomba nests the checks in `data.email`, uses JSON booleans, and lists the public pages where the address appears in `data.sources`. Field definitions are in [Email verifier response](/attributes/verifier).

| DeBounce                           | Tomba                                     | Notes                                                                       |
| ---------------------------------- | ----------------------------------------- | --------------------------------------------------------------------------- |
| `debounce.email`                   | `data.email.email`                        | An empty string when the domain is disposable.                              |
| `debounce.code`, `debounce.reason` | `data.email.status`                       | See [Status mapping](#status-mapping).                                      |
| `debounce.result`                  | `data.email.result`                       | The closest field; see [Result values](/attributes/verifier#result-values). |
| `debounce.free_email`              | `data.email.webmail`                      |                                                                             |
| `debounce.role`                    | No equivalent                             |                                                                             |
| `debounce.send_transactional`      | No equivalent                             |                                                                             |
| `debounce.did_you_mean`            | No equivalent                             |                                                                             |
| `success`                          | HTTP status code                          | See [Error mapping](#error-mapping).                                        |
| `balance`                          | `X-Verify-Remaining` header, `GET /v1/me` | See [Check usage](/usage-and-quotas#check-usage).                           |
| `debounce.error`                   | `errors.message`                          |                                                                             |

Tomba also returns `score`, `accept_all`, `block`, `greylisted`, `gibberish`, `disposable`, `mx_check`, `mx.records`, `smtp_provider`, `regex`, and `whois`, which have no field in DeBounce's response.

## Status mapping

| DeBounce `code` | `reason`    | Tomba `status`                                          | Tomba `result`  |
| --------------- | ----------- | ------------------------------------------------------- | --------------- |
| `1`             | Syntax      | No status. The request fails with `422 params_invalid`. |                 |
| `2`             | Spam Trap   | No equivalent. Tomba returns the mailbox result.        |                 |
| `3`             | Disposable  | `disposable`                                            | Empty string    |
| `4`             | Accept-All  | `accept_all`                                            | `risky`         |
| `5`             | Deliverable | `valid`                                                 | `deliverable`   |
| `6`             | Invalid     | `invalid`                                               | `undeliverable` |
| `7`             | Unknown     | `unknown`                                               | `risky`         |

DeBounce reports role accounts in the `role` field rather than as code `8` in API responses; Tomba has no role flag. Compare `status` case-insensitively. Each value is defined in [Status values](/attributes/verifier#status-values).

## Error mapping

Both APIs signal failures with HTTP status codes. DeBounce's error body is `{"debounce": {"error", "code"}, "success": "0"}`; Tomba's is an `errors` object with `type`, `message`, and `code`.

| DeBounce                                           | Tomba                                                                                                                        | What to do                                                                                          |
| -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `401` `Wrong API`                                  | `400 authentication_failed` (missing or malformed), `400 api_key_expired`, `401 authentication_failed` (wrong key or secret) | Check both headers; [rotate](/authentication#rotate-keys) an expired key.                           |
| `402` `Credits Low`                                | `402 quota_exceeded` on single requests                                                                                      | For bulk jobs, the balance is checked at the first download instead; see [Billing](/bulks#billing). |
| `429` `Maximum concurrent calls reached`           | `429 rate_limit`                                                                                                             | Retry after the `Retry-After` delay; see [Handle 429 responses](/rate-limits#handle-429-responses). |
| Maximum number of API bulk verify requests reached | `429 rate_limit` (running job limit)                                                                                         | Wait for a running job to finish; see [Limits](/bulks#limits).                                      |
| `400` `URL parameter is not valid.`                | No equivalent                                                                                                                | Upload the file instead of a URL.                                                                   |
| `List ID is not valid.`                            | `GET /v1/bulk/verifier/{id}` returns `404 unknown_record`                                                                    | Check the job ID.                                                                                   |
| No equivalent                                      | `422 params_invalid`                                                                                                         | Fix the parameter named in the message.                                                             |
| No equivalent                                      | `408 proxy_error`                                                                                                            | The mailbox check failed. Retry later.                                                              |
| No equivalent                                      | `451 claimed_email`                                                                                                          | The owner of the address asked Tomba to stop processing it. Remove it from your list.               |

Error responses don't consume credits. Every type is listed in [Errors](/error-handling#error-types).

## Bulk and async jobs

| DeBounce                                                       | Tomba                                                                                                |
| -------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `/v1/upload/` with the `url` of a file on your server          | `POST /v1/bulk/verifier` with `name` and a CSV `file`, or a newline-separated `list` of addresses    |
| Validation starts on upload, or is queued behind a running job | Launch the job: `PUT /v1/bulk/verifier/{id}`                                                         |
| Poll `/v1/status/` for `status` and `percentage`               | Poll `GET /v1/bulk/verifier/{id}/progress` for `status` and `progress` until `status` is `completed` |
| Fetch the CSV from `download_link`                             | `GET /v1/bulk/verifier/{id}/download?file=full`                                                      |

```bash
curl -X POST "https://api.tomba.io/v1/bulk/verifier" \
  -H "X-Tomba-Key: $TOMBA_API_KEY" \
  -H "X-Tomba-Secret: $TOMBA_SECRET_KEY" \
  -F "name=Newsletter list" \
  -F "file=@emails.csv;type=text/csv" \
  -F "email_field_index=0"
```

Tomba treats the first row of the file as a header. A DeBounce list with one address per line and no header loses its first address, so add a header row first. The launch, progress, and download requests are shown in [Lifecycle](/bulks#lifecycle). Row limits and daily job limits are in [Limits](/bulks#limits), and a job is charged when you first download its results; see [Billing](/bulks#billing).

## Code example

This function replaces a call to `api.debounce.io/v1/` and returns DeBounce's `code` and `reason` for the address. It needs Node.js 18 or later.

```js title="validate.mjs"
const TOMBA_HEADERS = {
    "X-Tomba-Key": process.env.TOMBA_API_KEY,
    "X-Tomba-Secret": process.env.TOMBA_SECRET_KEY,
};

// Tomba status -> DeBounce [code, reason]
const CODES = {
    disposable: ["3", "Disposable"],
    accept_all: ["4", "Accept-All"],
    valid: ["5", "Deliverable"],
    invalid: ["6", "Invalid"],
    unknown: ["7", "Unknown"],
};

async function validate(email) {
    const url = new URL("https://api.tomba.io/v1/email-verifier");
    url.searchParams.set("email", email);
    const res = await fetch(url, { headers: TOMBA_HEADERS });
    const body = await res.json();
    if (body.errors) {
        // A malformed address (DeBounce code 1) fails with 422 params_invalid.
        throw new Error(
            `${res.status} ${body.errors.type}: ${body.errors.message}`,
        );
    }

    const check = body.data.email;
    const [code, reason] = CODES[check.status.toLowerCase()] ?? CODES.unknown;
    return {
        email: check.email || email,
        code,
        reason,
        free_email: String(check.webmail),
    };
}

console.log(await validate("jane.doe@stripe.com"));
```

## Cutover checklist

1. Store the Tomba key and secret as `TOMBA_API_KEY` and `TOMBA_SECRET_KEY`, send them as `X-Tomba-Key` and `X-Tomba-Secret`, and remove the `api` parameter from your URLs and logs.
2. Point single validations at `GET https://api.tomba.io/v1/email-verifier` and set a client timeout of at least 180 seconds.
3. Replace checks of `success` with HTTP status handling: `402`, `422`, `429` with `Retry-After`, `408`, and `451`.
4. Translate `code` with the [status mapping](#status-mapping), comparing `status` case-insensitively, and read Tomba's booleans as JSON booleans rather than strings.
5. Replace URL uploads with `verifier` bulk jobs: upload the CSV with a header row, launch, poll `/progress`, and download the CSV.
6. Verify the same sample list with both services and compare the mapped results before you switch production traffic.
