Bulk operations
A bulk job runs one Tomba operation over a list. You create the job with your input, launch it, follow its progress, and download the results as CSV. Credits are charged when you first download the results; see Billing.
Every bulk endpoint takes the bulk type in its path, as in POST /v1/bulk/{type}, and accepts the same credentials as the rest of the API; OAuth tokens need the leads scope. Jobs are shared by all members of a workspace.
Bulk types
| Type | {type} | Input | Maximum rows |
|---|---|---|---|
| Domain search | search | Domains or company names | 25,000 |
| Similar domains | similar | Domains or company names | 5,000 |
| Company enrichment | company | Domains or company names | 25,000 |
| Email finder | finder | Names, with a domain or company name | 10,000 |
| Email enrichment | enrich | Email addresses | 20,000 |
| LinkedIn finder | linkedin | LinkedIn profile URLs | 20,000 |
| Author finder | author | Article URLs | 5,000 |
| Email verifier | verifier | Email addresses | 20,000 |
| Phone finder | phone-finder | Email addresses, LinkedIn URLs, or domains | 10,000 |
| Phone validator | phone-validator | Phone numbers, with an optional country | 10,000 |
| Websites by technology | technology | Technology slugs, such as react | 10 |
| Domain export | export | One domain | 1 |
Rows beyond the maximum aren't processed. GET /v1/bulk/types returns every type with its fields, options, and limits.
Domain search
Returns the email addresses of each domain, or of each company name with input_type set to company. It takes the options input_type, max_emails_per_domain, email_type, departments, verify_emails, find_phones, include_sources, and boost_score_from_sources; see Options.
verify_emails is turned off, without an error, when max_emails_per_domain is above 10 or the input has more than 2,000 rows.
Code
Similar domains
Returns companies similar to each domain, with their email addresses. It takes the options input_type and max_emails_per_domain.
Company enrichment
Returns a company profile for each domain, or for each company name with input_type set to company. It takes the options input_type and find_phones.
Email finder
Returns one address per person. Send data or a CSV file with a first and last name, or a full name, and a domain or company name; a list isn't accepted. It takes the options find_phones, include_sources, and skip_rows_with_email.
Code
With a header row such as first_name,last_name,company_website, the columns are found by name. For other headers, set first_name_field_index, last_name_field_index, and domain_field_index; see Columns.
Email enrichment
Returns person and company data for each email address. It takes the options find_phones and include_sources.
LinkedIn finder
Returns the email address for each profile URL, in the form https://www.linkedin.com/in/<profile>. It takes the options find_phones and include_sources.
Author finder
Returns the author's email address for each article URL. Use article URLs, not home pages. It takes the option include_sources.
Email verifier
Returns the verification result for each address. Verification is always on for this type. It takes the options find_phones and include_sources.
Code
Phone finder
Returns a phone number for each email address, LinkedIn profile URL, or domain. A list can mix the three. In a file with several of these columns, they're found from the header, or set email_field_index, linkedin_url_field_index, or domain_field_index. With find_all_phones set to true, every number found is returned instead of one. Jobs of this type have the bulk_type phone.
Phone validator
Returns validity, line type, and carrier for each phone number. In a file, the phone number column and an optional country column are found from the header, or set phone_field_index and country_field_index.
Websites by technology
Returns the websites that use each technology, given as slugs such as react or shopify, up to 10 per job. Each technology returns at most as many websites as your plan's search credits when you create the job.
Domain export
Exports the email addresses of one domain. Send domain instead of an input, and optionally filter: all (the default), personal, generic, or a department. It takes the options find_phones and include_sources, and name defaults to the domain.
A domain export needs a paid plan and starts when it's created. It has three routes: create, progress, and download, with the full file only.
Create a job
POST /v1/bulk/{type} with a JSON body, or with a multipart/form-data form to upload a CSV file. Every parameter works in both; the API reference describes each one.
| Parameter | Description |
|---|---|
name | 3 to 200 characters. Required, except for a domain export. |
list, data, file | The input; see Input. |
has_header | false when the first row of data or of the file isn't a header. Default true. |
delimiter | Column separator of the file: , (the default), ;, |, or tab. |
*_field_index | Zero-based column of a field; see Columns. |
| Options | Depend on the type; see Options. |
notify | true emails you when the job finishes with at least one result. |
launch | true launches the job right after creating it; see Lifecycle. |
webhook_url | An https:// URL that receives an event when the job ends; see Bulk job events. |
A field index or an option that the type doesn't use returns 422 params_invalid, except verify_emails, which is ignored. Other unknown parameters are ignored, including the names used before September 30, 2026, such as maximum, verify, sources, notifie, column, and column_first; see the changelog.
Input
Send the input in one of three ways:
list: one value per line, as a string or a JSON array. Tomba trims, lowercases, and de-duplicates the values, drops the invalid ones, and keeps the first values up to the row limit. The email finder doesn't accept a list.data: an array of rows, each an array of cells, for input with several columns. The first row is a header unlesshas_headerisfalse. In a multipart form, send it as a JSON-encoded string.file: a CSV file of up to 4 MB, in amultipart/form-datarequest. The first row is a header unlesshas_headerisfalse. Save spreadsheets as CSV first:.xlsx,.xls,.numbers, and.odsfiles are refused.
Domain search, similar domains, company enrichment, and websites by technology clean the values of data and file as they clean a list. Sending both data and file returns 422. A list with no valid value returns 400 unknown_record.
Columns
With data or file, Tomba finds each column from the header row: a header such as Company Website, domain, or url is read as the domain. Matching ignores case, spaces, and punctuation. To choose a column yourself, set its field index to the column's zero-based position: the first column is 0. Input with a single column needs neither.
| Field | Parameter | Types |
|---|---|---|
| Domain | domain_field_index | search, similar, company, finder, phone-finder |
| Company name | company_name_field_index | search, similar, company, finder |
| First name, last name | first_name_field_index, last_name_field_index | finder |
| Full name | full_name_field_index | finder |
| Email address | email_field_index | enrich, verifier, phone-finder |
| LinkedIn profile URL | linkedin_url_field_index | linkedin, phone-finder |
| Article URL | url_field_index | author |
| Phone number, country | phone_field_index, country_field_index | phone-validator |
| Technology | technology_field_index | technology |
GET /v1/bulk/types lists the header names matched for each field in fields[].header_synonyms. When a required column isn't found, the request fails with 422 params_invalid, and the message names the parameter to set.
Options
| Option | Types | Effect |
|---|---|---|
input_type | search, similar, company | domain or company. By default, company when the input has a company name column and no domain column, and domain otherwise. |
max_emails_per_domain | search, similar | Addresses per domain: 1, 10 (the default), 20, 50, or 100. |
email_type, email_type_mode | search | email_type is all (the default), personal, or generic. With email_type_mode only (the default), only that type is returned; with priority, every type is. |
departments, departments_mode | search | Department names, such as ["sales", "marketing"]. With departments_mode only (the default), only those departments are returned; with priority, every department is. The values are in the API reference. |
verify_emails | search | true verifies the addresses found. |
find_phones | search, company, finder, enrich, linkedin, verifier, export | true adds phone numbers, at an extra cost. |
include_sources | search, finder, enrich, linkedin, author, verifier, export | true adds the web pages where each address was found. |
boost_score_from_sources | search | true adds 10 points, up to 90, to the score of an address that couldn't be verified but was found on at least 5 web pages. |
skip_rows_with_email | finder | true skips the rows that already have a value in a column named email. |
find_all_phones | phone-finder | true returns every phone number found instead of one. |
filter | export | all (the default), personal, generic, or a department name. |
GET /v1/bulk/types also lists options that some types accept without using
them, such as email_type for similar domains. They have no effect, except
verify_emails: on similar domains, company enrichment, email finder, email
enrichment, LinkedIn finder, and author finder jobs, it adds verification
credits without verifying anything. See Billing.
Lifecycle
A job is pending until it's launched, then running, and it ends completed, failed, or cancelled. The examples use the search type; replace it with the type of your job. Every operation is in the API reference.
-
Create the job. The response contains its ID:
CodeWith
"launch": true, the job starts right away. If it can't, it stayspending, anddata.messagesays why:Code -
Launch it, unless you set
launch. Only apendingjob can be launched; any other status returns409 invalid_state. The launch fails with429 rate_limitwhile 2 jobs of the type are running, or when a kind of credit the job needs is used up.Code -
Follow it. Poll its progress until
statusiscompleted,failed, orcancelled, or setwebhook_urlto receive an event when it ends; see Bulk job events. The progress isn't wrapped indata, and it also includesmetrics, the results so far:CodeCode -
Download the results as CSV once the job is
completed:CodefileFile fullEvery result. The default, and the only file of a domain export. validOnly rows with a valid result. Not available for company enrichment and similar domains. not_foundInput rows that returned no result. Not available for email finder jobs, or for email verifier jobs created from a list.A job that isn't
completedreturns409 invalid_state, and a file that doesn't exist returns404 api_error.
A job expires 180 days after it's created, at expired_at. From then on, expired is true, and downloading, launching, or retrying the job returns 409 invalid_state: download the results before that date.
To manage jobs:
| Operation | Request |
|---|---|
| List jobs | GET /v1/bulk/{type}?page=1&limit=10, newest first. limit is at most 50. Filter with status, with q for text in the name, with expired=true or expired=false, and with archived=true for archived jobs. |
| Get a job | GET /v1/bulk/{type}/{id}. A job that doesn't exist or was deleted returns 404 unknown_record. |
| Estimate its cost | GET /v1/bulk/{type}/{id}/estimate; see Billing. |
| Read its history | GET /v1/bulk/{type}/{id}/history: its events, such as the launch, downloads, and webhook deliveries, oldest first. |
| Cancel a job | POST /v1/bulk/{type}/{id}/cancel. Only a pending job can be cancelled. |
| Retry a job | POST /v1/bulk/{type}/{id}/retry. Runs a failed or cancelled job again from its input, and launches it. |
| Rename a job | PUT /v1/bulk/{type}/{id}/rename with {"name": "New name"} |
| Archive a job | DELETE /v1/bulk/{type}/{id}/archive, for a completed, failed, or cancelled job. Restore it with PUT /v1/bulk/{type}/{id}/restore. |
| Delete a job | DELETE /v1/bulk/{type}/{id}/delete |
GET /v1/bulk/stats sums your bulk activity over a period, the last 30 days by default: jobs by status, rows processed, results, and credits billed, in total, per type, and per day.
Job fields
GET /v1/bulk/{type}/{id} returns the job in data. GET /v1/bulk/{type} returns a list of jobs, without preview.
| Field | Description |
|---|---|
bulk_id | Job ID |
bulk_type | The type. Phone finder jobs are phone. |
name, user_id | The job's name, and the user who created it |
status | pending, running, completed, failed, or cancelled |
progress, processed | Percentage and number of rows processed |
billed | true once the results were downloaded and the job charged |
error_message, retry_count | Why the job failed, and how many times it was retried |
created_at, started_at, completed_at, cancelled_at | When the job was created, started, completed, and cancelled |
archived, expired, expired_at | Whether the job is archived, whether it has expired, and when it expires |
config | The input and options: input.rows, input.fields (the zero-based column of each field), options, notify, and webhook.url |
metrics | The results: rows, results (found, not_found, hit_rate, and records), distribution, emails, phones, credits, and timing |
preview | The first result rows, once billed is true |
The fields of config and metrics are described in the API reference.
Limits
These limits apply per workspace, or per user outside a workspace. Bulk endpoints aren't subject to the plan rate limits.
| Limit | Value |
|---|---|
| Jobs created | 15 per day for each bulk type, and 30 domain exports |
| Running jobs | 2 per bulk type. While 2 are running, no job of that type can be created, launched, or retried. |
| Downloads | 25 per day for each job, and 30 for a domain export |
| Request size | 4 MB. Larger requests fail with 413, and a larger file with 400. |
| Rows | See Bulk types |
Exceeding a daily limit returns 429 rate_limit with a Retry-After header. The running-job limit returns 429 rate_limit with the message "You already have 2 Domain Search bulks running. Wait for one to finish, then launch this one.", naming the type.
Billing
Nothing is charged when you create or launch a job. At launch, Tomba checks that you have credits of each kind the job needs; if one is used up, the launch fails with 429 rate_limit. The job's full cost is charged the first time you download any of its results; later downloads are free. The duplicate-request rule doesn't apply, so running the same list again is charged again.
Before the first download, Tomba checks your balance. If it's lower than the job's cost, the download fails with 429 rate_limit and a message that gives the remaining balance and the cost.
GET /v1/bulk/{type}/{id}/estimate returns the cost and your remaining balance: an upper bound before the job completes (basis is upper_bound), and the exact charge after (exact). A completed job also shows its charge in metrics.credits.
| Type | Search credits | Verification credits |
|---|---|---|
| Domain search | Per domain with at least one address: 1 per 10 addresses returned, at most 10. Plus 5 per address with phone data. | With verify_emails: 1 per address returned |
| Similar domains | 1 per domain with at least one similar company | — |
| Company enrichment | 1 per company found. Plus 1 per company with phone data. | — |
| Email finder | 1 per row whose address is verified valid. Plus 5 per row with phone data. | — |
| Email enrichment | 1 per row with a result. Plus 5 per row with phone data. | — |
| LinkedIn finder | 1 per row with a result. Plus 5 per row with phone data. | — |
| Author finder | 1 per article with an author address | — |
| Email verifier | 5 per row with phone data | 1 per row, except disposable addresses |
| Phone finder | 5 per row with at least one phone number | — |
| Phone validator | 1 per valid number | — |
| Websites by technology | 1 per 10 websites found, rounded up | — |
| Domain export | 1 per 10 addresses, rounded up. Plus 5 per address with phone data. | — |
Set verify_emails only on domain search jobs. On similar domains, company enrichment, email finder, email enrichment, LinkedIn finder, and author finder jobs, it doesn't run a verification but adds one verification credit per result row.
Errors
| Status | errors.type | Cause |
|---|---|---|
400 | api_error | The JSON body or the CSV file can't be read, or the file is over 4 MB |
400 | unknown_record | No value of the list is valid for the type |
404 | unknown_record | No job of this type has this ID in your account or workspace, or it was deleted |
404 | api_error | The job has no result file of the requested kind |
409 | invalid_state | The job's status doesn't allow the action, such as launching a running job, or the job has expired |
413 | — | The request is over 4 MB |
422 | params_invalid | A parameter is missing or invalid, or the type doesn't use it. The message names it. |
429 | rate_limit | A daily limit, the running-job limit, or your credit balance. See Limits and Billing. |
The response format and the other error types are in Errors.