TombaTomba
Start free
  • Documentation
  • API reference
  • SDKs & CLI
  • AI & MCP
  • Integrations
  • Enterprise
  • Migration guides

Find, verify and enrich B2B contacts from public sources, in the app, your spreadsheet or through the API.

GDPR & CCPA

// docs

  • API changelog
  • Support options
  • Security

// platform

  • Dashboard
  • Pricing
  • API status

// resources

  • Help center
  • Product updates
  • TombaBot crawler

// company

  • Website
  • Contact us
  • Edit or remove your data

© 2021–2026 Tomba.io

  • Terms of service
  • Privacy policy
  • Cookie policy
  • Security policy
  • GDPR
  • CCPA
Get started
Platform
API guides
    Bulk operationsLogo APIData sources
Response reference
Policies
Resources
API guides

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}InputMaximum rows
Domain searchsearchDomains or company names25,000
Similar domainssimilarDomains or company names5,000
Company enrichmentcompanyDomains or company names25,000
Email finderfinderNames, with a domain or company name10,000
Email enrichmentenrichEmail addresses20,000
LinkedIn finderlinkedinLinkedIn profile URLs20,000
Author finderauthorArticle URLs5,000
Email verifierverifierEmail addresses20,000
Phone finderphone-finderEmail addresses, LinkedIn URLs, or domains10,000
Phone validatorphone-validatorPhone numbers, with an optional country10,000
Websites by technologytechnologyTechnology slugs, such as react10
Domain exportexportOne domain1

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.

TerminalCode
curl -X POST "https://api.tomba.io/v1/bulk/search" \ -H "X-Tomba-Key: $TOMBA_API_KEY" \ -H "X-Tomba-Secret: $TOMBA_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Q3 target accounts", "list": "stripe.com\nshopify.com\nnotion.so", "max_emails_per_domain": 10, "email_type": "personal", "verify_emails": true, "launch": true }'

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.

TerminalCode
curl -X POST "https://api.tomba.io/v1/bulk/finder" \ -H "X-Tomba-Key: $TOMBA_API_KEY" \ -H "X-Tomba-Secret: $TOMBA_SECRET_KEY" \ -F "name=Conference leads" \ -F "file=@leads.csv;type=text/csv" \ -F "launch=true"

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.

TerminalCode
curl -X POST "https://api.tomba.io/v1/bulk/verifier" \ -H "X-Tomba-Key: $TOMBA_API_KEY" \ -H "X-Tomba-Secret: $TOMBA_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Newsletter list", "list": ["jane.doe@stripe.com", "john.doe@shopify.com"], "launch": true, "webhook_url": "https://hooks.example.com/tomba/7f3c9a" }'

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.

ParameterDescription
name3 to 200 characters. Required, except for a domain export.
list, data, fileThe input; see Input.
has_headerfalse when the first row of data or of the file isn't a header. Default true.
delimiterColumn separator of the file: , (the default), ;, |, or tab.
*_field_indexZero-based column of a field; see Columns.
OptionsDepend on the type; see Options.
notifytrue emails you when the job finishes with at least one result.
launchtrue launches the job right after creating it; see Lifecycle.
webhook_urlAn 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 unless has_header is false. In a multipart form, send it as a JSON-encoded string.
  • file: a CSV file of up to 4 MB, in a multipart/form-data request. The first row is a header unless has_header is false. Save spreadsheets as CSV first: .xlsx, .xls, .numbers, and .ods files 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.

FieldParameterTypes
Domaindomain_field_indexsearch, similar, company, finder, phone-finder
Company namecompany_name_field_indexsearch, similar, company, finder
First name, last namefirst_name_field_index, last_name_field_indexfinder
Full namefull_name_field_indexfinder
Email addressemail_field_indexenrich, verifier, phone-finder
LinkedIn profile URLlinkedin_url_field_indexlinkedin, phone-finder
Article URLurl_field_indexauthor
Phone number, countryphone_field_index, country_field_indexphone-validator
Technologytechnology_field_indextechnology

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

OptionTypesEffect
input_typesearch, similar, companydomain or company. By default, company when the input has a company name column and no domain column, and domain otherwise.
max_emails_per_domainsearch, similarAddresses per domain: 1, 10 (the default), 20, 50, or 100.
email_type, email_type_modesearchemail_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_modesearchDepartment 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_emailssearchtrue verifies the addresses found.
find_phonessearch, company, finder, enrich, linkedin, verifier, exporttrue adds phone numbers, at an extra cost.
include_sourcessearch, finder, enrich, linkedin, author, verifier, exporttrue adds the web pages where each address was found.
boost_score_from_sourcessearchtrue 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_emailfindertrue skips the rows that already have a value in a column named email.
find_all_phonesphone-findertrue returns every phone number found instead of one.
filterexportall (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.

  1. Create the job. The response contains its ID:

    Code
    { "data": { "id": 4521 } }

    With "launch": true, the job starts right away. If it can't, it stays pending, and data.message says why:

    Code
    { "data": { "id": 4521, "message": "Bulk created but not launched: You already have 2 Domain Search bulks running. Wait for one to finish, then launch this one." } }
  2. Launch it, unless you set launch. Only a pending job can be launched; any other status returns 409 invalid_state. The launch fails with 429 rate_limit while 2 jobs of the type are running, or when a kind of credit the job needs is used up.

    TerminalCode
    curl -X PUT "https://api.tomba.io/v1/bulk/search/4521" \ -H "X-Tomba-Key: $TOMBA_API_KEY" \ -H "X-Tomba-Secret: $TOMBA_SECRET_KEY"
  3. Follow it. Poll its progress until status is completed, failed, or cancelled, or set webhook_url to receive an event when it ends; see Bulk job events. The progress isn't wrapped in data, and it also includes metrics, the results so far:

    TerminalCode
    curl "https://api.tomba.io/v1/bulk/search/4521/progress" \ -H "X-Tomba-Key: $TOMBA_API_KEY" \ -H "X-Tomba-Secret: $TOMBA_SECRET_KEY"
    Code
    { "status": "running", "progress": 42, "processed": 1260, "error_message": null, "rows_per_minute": 420, "eta_seconds": 248 }
  4. Download the results as CSV once the job is completed:

    TerminalCode
    curl -o results.csv \ "https://api.tomba.io/v1/bulk/search/4521/download?file=full" \ -H "X-Tomba-Key: $TOMBA_API_KEY" \ -H "X-Tomba-Secret: $TOMBA_SECRET_KEY"
    fileFile
    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 completed returns 409 invalid_state, and a file that doesn't exist returns 404 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:

OperationRequest
List jobsGET /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 jobGET /v1/bulk/{type}/{id}. A job that doesn't exist or was deleted returns 404 unknown_record.
Estimate its costGET /v1/bulk/{type}/{id}/estimate; see Billing.
Read its historyGET /v1/bulk/{type}/{id}/history: its events, such as the launch, downloads, and webhook deliveries, oldest first.
Cancel a jobPOST /v1/bulk/{type}/{id}/cancel. Only a pending job can be cancelled.
Retry a jobPOST /v1/bulk/{type}/{id}/retry. Runs a failed or cancelled job again from its input, and launches it.
Rename a jobPUT /v1/bulk/{type}/{id}/rename with {"name": "New name"}
Archive a jobDELETE /v1/bulk/{type}/{id}/archive, for a completed, failed, or cancelled job. Restore it with PUT /v1/bulk/{type}/{id}/restore.
Delete a jobDELETE /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.

FieldDescription
bulk_idJob ID
bulk_typeThe type. Phone finder jobs are phone.
name, user_idThe job's name, and the user who created it
statuspending, running, completed, failed, or cancelled
progress, processedPercentage and number of rows processed
billedtrue once the results were downloaded and the job charged
error_message, retry_countWhy the job failed, and how many times it was retried
created_at, started_at, completed_at, cancelled_atWhen the job was created, started, completed, and cancelled
archived, expired, expired_atWhether the job is archived, whether it has expired, and when it expires
configThe input and options: input.rows, input.fields (the zero-based column of each field), options, notify, and webhook.url
metricsThe results: rows, results (found, not_found, hit_rate, and records), distribution, emails, phones, credits, and timing
previewThe 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.

LimitValue
Jobs created15 per day for each bulk type, and 30 domain exports
Running jobs2 per bulk type. While 2 are running, no job of that type can be created, launched, or retried.
Downloads25 per day for each job, and 30 for a domain export
Request size4 MB. Larger requests fail with 413, and a larger file with 400.
RowsSee 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.

TypeSearch creditsVerification credits
Domain searchPer 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 domains1 per domain with at least one similar company—
Company enrichment1 per company found. Plus 1 per company with phone data.—
Email finder1 per row whose address is verified valid. Plus 5 per row with phone data.—
Email enrichment1 per row with a result. Plus 5 per row with phone data.—
LinkedIn finder1 per row with a result. Plus 5 per row with phone data.—
Author finder1 per article with an author address—
Email verifier5 per row with phone data1 per row, except disposable addresses
Phone finder5 per row with at least one phone number—
Phone validator1 per valid number—
Websites by technology1 per 10 websites found, rounded up—
Domain export1 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

Statuserrors.typeCause
400api_errorThe JSON body or the CSV file can't be read, or the file is over 4 MB
400unknown_recordNo value of the list is valid for the type
404unknown_recordNo job of this type has this ID in your account or workspace, or it was deleted
404api_errorThe job has no result file of the requested kind
409invalid_stateThe 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
422params_invalidA parameter is missing or invalid, or the type doesn't use it. The message names it.
429rate_limitA 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.

Last modified on September 30, 2026
All limitsLogo API
On this page
  • Bulk types
    • Domain search
    • Similar domains
    • Company enrichment
    • Email finder
    • Email enrichment
    • LinkedIn finder
    • Author finder
    • Email verifier
    • Phone finder
    • Phone validator
    • Websites by technology
    • Domain export
  • Create a job
    • Input
    • Columns
    • Options
  • Lifecycle
  • Job fields
  • Limits
  • Billing
  • Errors
JSON
JSON
JSON