# Going to production

Work through each section before you send production traffic to the Tomba API. Each item links to the page that defines the behavior.

## Timeouts

- Set your HTTP client timeout to 180 seconds. Searches and verifications run in real time against the target's mail servers and websites, so a single request can take that long.
- A request that runs longer than 180 seconds ends with `504` from the gateway. The body isn't JSON and has no `errors` object.
- The request body can be at most 4 MB. A larger body fails with `413` and `{"error": "Request Entity Too Large"}`.
- For lists of inputs, use [bulk jobs](/bulks) instead of long-running loops of single requests.

## Retries

Retry only the statuses that [Errors](/error-handling#status-codes) marks as retryable:

| Status | Retry                                                                                                                                                                                                                            |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `408`  | Later, with backoff.                                                                                                                                                                                                             |
| `429`  | After `Retry-After`, following [Handle 429 responses](/rate-limits#handle-429-responses). A `429` without `Retry-After` from email count, email format, location, or email sources means the balance is used up; don't retry it. |
| `500`  | With exponential backoff.                                                                                                                                                                                                        |
| `504`  | With backoff, or move the work to a [bulk job](/bulks).                                                                                                                                                                          |

Don't retry `400`, `401`, `402`, `403`, `404`, `409`, `413`, `422`, or `451`: the same request fails the same way until you change it or your account. The exception is a `422` with type `openai_error` or `search_failed` from company search, which you can retry.

Use exponential backoff with a cap and random jitter, and stop after a fixed number of attempts. Failed requests aren't charged; see [Credits and usage](/usage-and-quotas#credit-costs).

The API has no idempotency keys. Retrying a `GET` is safe, but retrying a request that creates something can create it twice. Before you retry one of these after a timeout or `5xx`, list the resource to check whether the first attempt succeeded:

| Request                | A retry can                                                                                           |
| ---------------------- | ----------------------------------------------------------------------------------------------------- |
| `POST /v1/bulk/{type}` | Create a second job, which counts toward the [bulk limits](/bulks#limits)                             |
| `POST /v1/leads`       | Create a second lead with the same address                                                            |
| `POST /v1/leads_lists` | Fail with `422 params_invalid` when the first attempt created the list, because list names are unique |
| `POST /v1/keys`        | Create a second API key                                                                               |
| `PUT /v1/keys/{id}`    | Rotate the key again, invalidating the value from the first attempt                                   |
| `POST /v1/flag`        | Return `409 duplicate_record` when the first report was stored; treat it as stored                    |

## Repeated requests

Some repeats of a successful request are free. The [duplicate-request rule](/usage-and-quotas#duplicate-requests) defines which ones, and for how long. Read it before you add your own cache: a cache keyed on different parameters than the rule can cost credits the rule would have waived.

## Request IDs

Responses from the API carry an `X-Request-ID` header. Log it with each request; quote it when you [contact support](/support#what-to-include) about a failed request.

You can also set the ID yourself. Send an `X-Request-ID` header of printable ASCII characters and the API returns the same value. An empty value, or one with other characters, is replaced by a generated ID.

```bash
curl -i "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" \
  -H "X-Request-ID: order-sync-7f3c9a"
```

A `504` from the gateway doesn't carry the header. Sending your own ID gives you a value to quote in that case too.

## Pagination

Paginated endpoints take a 1-based `page`. None accepts an offset. The page size and the names of the pagination fields differ between endpoints:

| Endpoint                                                 | Parameters                                                                                                               | Pagination fields                                                                     |
| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- |
| [`GET /v1/domain-search`](/api/finder#domain-search)     | `page`; `limit` from a fixed set of values, default 10. The [API reference](/api/finder#domain-search) lists the values. | `meta.total`, `meta.pageSize`, `meta.current`, `meta.total_pages`                     |
| [`GET /v1/similar`](/api/domain#similar)                 | `page`. Pages always hold 25 companies; `limit` only sets the credit cost.                                               | `meta.total`, `meta.pageSize`, `meta.current`, `meta.total_pages`                     |
| [`POST /v1/reveal/search`](/api/reveal#search-companies) | `page` in the JSON body, 1 to 1,000. Pages always hold 50 companies.                                                     | `meta.total`, `meta.page`, `meta.limit`, `meta.pages`                                 |
| [`GET /v1/leads`](/api/leads#retrieve-leads)             | `page`; `limit` default 10. Values above 200 are treated as 200.                                                         | `data.meta.total`, `data.meta.pageSize`, `data.meta.current`, `data.meta.total_pages` |
| [`GET /v1/bulk/{type}`](/api/bulks#list-bulk-jobs)       | `page`; `limit` default 10. Values above 50 are treated as 50.                                                           | `meta.total`, `meta.pageSize`, `meta.current`, `meta.total_pages`                     |
| [`GET /v1/logs`](/api/account#retrieve-api-logs)         | `page`; `limit` 1 to 100, default 20. Larger values fail with `422`.                                                     | `meta.total`, `meta.page`, `meta.limit`, `meta.total_pages`                           |

- On `GET /v1/leads` and `GET /v1/bulk/{type}`, `total_pages` is rounded down, so it can miss the last, partial page. Keep requesting pages until one returns fewer items than `limit`.
- On domain search and similar domains, a larger `limit` costs more credits; see [Credit costs](/usage-and-quotas#credit-costs).
- On the Free plan, domain search and company search return only the first page; see [Free plan limits](/usage-and-quotas#free-plan-limits).

## Keys and secrets

- Keep the key and secret on your server, loaded from environment variables or a secrets manager. See [Security](/security#api-credentials).
- Use one key per integration, so you can rotate or delete one without touching the others.
- Every key expires. Track `expires_at` from [`GET /v1/keys`](/api/keys#retrieve-api-keys), and alert on the `X-Tomba-Key-Expired` header and the `api_key_expired` error. See [Key expiry](/authentication#key-expiry).
- Rotate keys before they expire, following [Rotate keys](/authentication#rotate-keys).

## Webhooks

Callbacks sent to a `webhook_url` aren't signed. Put a hard-to-guess token in the URL, reject requests that don't carry it, and respond with a `2xx` as soon as you've stored the payload. [Webhooks](/webhook#delivery) describes the delivery and retry behavior.

## Monitoring

- Subscribe to [status.tomba.io](https://status.tomba.io/) for incidents and outages.
- Track your throttling headroom from the rate limit headers, or poll [`GET /v1/rate-limits`](/rate-limits#check-your-limits), which isn't rate-limited itself.
- Track your credit balances with `GET /v1/me`, `GET /v1/usage`, or the quota headers on each response. See [Check usage](/usage-and-quotas#check-usage).
- Alert on rising `429`, `5xx`, and `504` rates, and on `402 quota_exceeded`, which means a balance is too low for the request.
