Async exports
Create an export from a search ID and poll for completion.
/functions/v1/api-export-createPreview before creating an export
Optional: POST api-export-preflight or call MCP preview_export with the same export intent. Requires export scope (hosted MCP also requires mcp). Preview is available for both People and Company search IDs.
curl -X POST "https://ttzqldekaizydrdmirpn.supabase.co/functions/v1/api-export-preflight" \
-H "Content-Type: application/json" \
-H "x-searchleads-api-key: sl_live_your_full_api_key_here" \
-d '{"search_id":"SEARCH_ID_FROM_SEARCH_RESPONSE","requested_leads":3,"format":"csv","fields":["name","workEmail"],"saveContacts":true}'Allowed JSON keys: search_id (required owned, unexpired successful search UUID), requested_leads (positive integer), format (csv only), fields (array of column keys), and saveContacts (JSON boolean). Omitted row count uses the smaller of the account limit and search snapshot count. Omitted columns use the safe-core default without email. Saving uses the current completed-export default; preview itself never saves records. Company requests must use Company column keys, for example name and country.
A valid preview returns HTTP 200 with can_export, blockers (code and message), requested_leads, max_exportable_rows, fields (normalized keys and readable labels), saving, credits, limits and delivery. Check can_export and resolve blockers before calling export creation. The server never silently reduces an explicitly requested row count.
availability.rows_at_searchis the search-time snapshot, not a fresh live count.as_of,snapshot_age_secondsandexpires_atshow its age/expiry.count_exact: truemeans exact at search time;falsemeans a lower bound;nullmeans historical exactness is unknown. An approximate count does not prove the full audience size.credits.maximum_required_holdis the maximum credit hold calculated with current prices and selected paid fields.credits.spendableandcredits.sufficientdescribe the current spendable balance. Final charges depend on delivered email/phone data and existing entitlements; the maximum hold is not a promised final charge.credits.chargedandcredits.reservedare zero. Preview creates no search session, export job, provider request, file or saved membership. Saving adds no separate surcharge.- Preview reserves neither credits nor records. Prices, balance, membership, limits and readiness may change;
start_export/api-export-createalways revalidate and may reject a previously ready preview. Preview is optional and is not a reservation token. - Preview uses one shared per-minute request slot across REST/MCP, has no daily cap, and returns the normal rate-limit headers in REST. Export creation and status remain unlimited by those customer request quotas. MCP uses
result.isErrorfor HTTP failures; HTTP 200 withcan_export: falseis a readable blocked preview, not a transport error.
Readiness blockers include insufficient_export_credits, export_size_exceeded, export_size_exceeds_search_total, search_total_zero, max_concurrent_jobs_reached, payment review, and unavailable delivery. Invalid/unsupported fields return 400; invalid typed values or format return 422; missing access returns 401/403; foreign/unknown search IDs return 404; expired IDs return 410; missing successful snapshots return 409; quota exhaustion returns 429; failed dependency reads return 503. Retry 429/503 with backoff; rerun a paid search only when a new search ID is intentionally needed.
The asynchronous export API uses a search_id returned by people or company search. It runs from page 0 of the saved canonical query. New searches also pin their server sort, so a limited export follows the search order. Existing snapshots without a pinned sort retain their original traversal. Create a job, then poll its status. Your client does not need to send a provider callback.
curl -X POST "https://ttzqldekaizydrdmirpn.supabase.co/functions/v1/api-export-create" \
-H "Content-Type: application/json" \
-H "x-searchleads-api-key: sl_live_your_full_api_key_here" \
-H "Idempotency-Key: my-export-2026-10-06" \
-d '{
"search_id": "SEARCH_ID_FROM_SEARCH_RESPONSE",
"requested_leads": 5000,
"format": "csv",
"fields": ["name", "title", "workEmail"],
"delivery_email": "ops@yourcompany.com"
}'
curl -X GET "https://ttzqldekaizydrdmirpn.supabase.co/functions/v1/api-export-status?job_id=JOB_ID" \
-H "x-searchleads-api-key: sl_live_your_full_api_key_here"You can provide an Idempotency-Key header or idempotency_key body field. If omitted, SearchLeads generates and returns one. Status polling is read-only.
Choose requested_leads between 1 and the smaller of your account's maximum and the saved search response's results.totalElements. Use the total, not results.content.length, which is only the current page. Do not create an export when the search is empty. A reported lower-bound or approximate total does not establish the entire audience size; the saved snapshot count is the exportable limit for that search_id.
The 5,000-row example above assumes at least 5,000 available records. An oversized request returns REST HTTP 422 with code: "export_size_exceeds_search_total" and available_records; MCP returns the same payload with result.isError: true. This refusal does not create an export job or hold additional export credits. For example, if available_records is 1, create a new intentional one-row request with a fresh idempotency key. Keep the original key and identical body when retrying an unchanged request after a timeout.
Choose fields explicitly. Omitting it uses a smaller safe-core API default that does not include email. Check capabilities for supported column keys, current maximum rows, job limits, and formats. Export credit cost depends on selected data and account configuration.
Save or do not save exported records
REST export creation and MCP start_export both accept saveContacts as a JSON boolean. It applies to both People and Company exports despite the parameter's name.
| Value | People export | Company export |
|---|---|---|
true | Add completed exported people to this account's Saved Contacts | Add completed exported companies to this account's Saved Companies |
false | Leave existing saved membership unchanged | Leave existing saved membership unchanged |
| Omitted | Use the configured People save default | Use the configured Company save default |
Check authenticated capabilities exports.saveRecordsBySearchType.people or .company for enabled, default, and the parameter name. If saving is disabled, the current service uses false even when true was requested; do not promise that those records were saved. Set an explicit boolean whenever your workflow needs a particular choice. false does not remove previously saved records.
{
"search_id": "REPLACE_WITH_RETURNED_SEARCH_ID",
"requested_leads": 1,
"fields": ["name", "linkedin"],
"format": "csv",
"saveContacts": true,
"idempotency_key": "REPLACE_WITH_ONE_KEY_FOR_THIS_EXPORT_INTENT"
}The saved search determines People versus Company and preserves any Saved-only or Net-New scope. Saving occurs as export records are completed/committed, rather than when the search runs. Wait for completion and saved-membership synchronization before expecting the records to disappear from a fresh Net New search. Saving does not alter an already-created search snapshot; run a new scoped search to observe the updated saved set. Previous exports are not backfilled. Keep the same body and key for an uncertain retry; intentionally changing the save choice creates a new export intent and requires a new key.
Export behavior and pricing
- API clients make two kinds of calls: create a job, then poll status. Provider callbacks are internal server-to-server messages. Status polling is read-only.
- UI, API, and MCP use the same configured fractional-credit prices for equivalent exports. Company exports use the configured company price per delivered row; for example, at 0.1 credit per row, one row costs 0.1 and eleven rows cost 1.1. Credit calculations retain two decimal places instead of rounding each export up to a whole credit. Read your account's current pricing and available/held credits before exporting.
- The export begins at page 0 of the saved canonical search, even if the
search_idcame from a later search page. Use the ID before itssearch_id_expires_attime. - A job holds the maximum selected-field charge, then settles based on delivered data. People rows with a valid Work Email use the account's export base cost plus its Work Email cost. A row with no requested paid contact data costs 0.5 credit when that policy is enabled. API and MCP exports have no separate channel surcharge.
- The account's saved default export template can override built-in Bulk Export selections. Explicit
fieldsmake integrations predictable. - If a provider job is stuck before pickup, SearchLeads support can retry dispatch internally; clients do not need an extra callback or retry parameter.
The capabilities endpoint reports allowed formats, maximum requested leads, concurrent jobs, and explicit unlimited create/status request quotas. Create and status requests have no customer request quotas and do not consume the shared search allowance. Concurrent jobs, credits, ownership checks and maximum export size remain enforced.
Available export columns
The live tables include every current People and Company column key (the exact CSV header), display label, group, kind, and built-in Bulk Export default. An API key loads your account's exact export permissions.
Legacy export integrations may accept additional historical column aliases. New integrations should use the current exports.fieldsBySearchType keys and must not infer additional fields from old CSV files.
Live export column reference
Explore the current CSV columns and export limits directly from the API.
Use your account’s contract Optional
Load your exact permissions and limits with a full API key. Your key stays in this page’s memory and is sent only to the SearchLeads API.
Concurrent jobs: 10. Credit billing and export size limits still apply.
People columns
73 of 73 columns shown. Default columns are marked.
| CSV header / request key | Display label | Group | Kind | Bulk default |
|---|---|---|---|---|
jobLocation | Job Location | Profile | text | — |
educationCompletionYear | Education Completion Year | Experience | list | — |
emailLastVerifiedDate | Email Last Verified Date | Contact | date | — |
contactMethodAvailable | Any Contact Method Available | Contact | boolean | — |
persona | Persona | Profile | list | — |
linkedinConnections | LinkedIn Connections | Profile | number | — |
patents | Patents | Experience | list | — |
publications | Publications | Experience | list | — |
companyHasCio | Company Has CIO | Digital signals | boolean | — |
companyHasCiso | Company Has CISO | Digital signals | boolean | — |
companyLocationCount | Company Number of Locations | Digital signals | number | — |
companyOrganicTraffic | Company Organic Traffic | Digital signals | number | — |
companyPaidTraffic | Company Paid Traffic | Digital signals | number | — |
companySicCodes | Company SIC Codes | Digital signals | list | — |
companyLegalType | Company Legal Structure | Digital signals | text | — |
companyWebsiteForSale | Company Website for Sale | Digital signals | boolean | — |
workEmail | Work Email | Contact | contact | Yes |
personalMobile | Personal Mobile | Contact | contact | — |
emailStatus | Email Status | Contact | text | Yes |
workEmailAvailable | Work Email Available | Contact | boolean | — |
personalMobileAvailable | Personal Mobile Available | Contact | boolean | — |
name | Full Name | Profile | text | Yes |
firstName | First Name | Profile | text | Yes |
lastName | Last Name | Profile | text | Yes |
headline | Headline | Profile | text | Yes |
title | Job Title | Profile | text | Yes |
seniority | Seniority | Profile | text | Yes |
department | Department | Profile | text | Yes |
linkedin | LinkedIn URL | Profile | text | Yes |
location | Location | Profile | text | Yes |
city | City | Profile | text | Yes |
state | State | Profile | text | Yes |
country | Country | Profile | text | Yes |
jobStartDate | Job Start Date | Experience | date | — |
skills | Skills | Experience | list | — |
certifications | Certifications | Experience | list | — |
personalWebsite | Personal Website | Contact | text | — |
educationDegree | Education Degree | Experience | list | — |
company | Company Name | Current company | text | Yes |
companyDomain | Company Domain | Current company | text | Yes |
companyWebsite | Company Website | Current company | text | Yes |
companyLinkedin | Company LinkedIn URL | Current company | text | Yes |
companyLocation | Company Location | Current company | text | Yes |
companyDescription | Company Description | Current company | text | — |
companyIndustry | Company Industry | Current company | text | Yes |
companyNaics | Company NAICS | Current company | text | Yes |
companyType | Company Type | Current company | text | Yes |
companyEmployees | Company Employees | Current company | number | Yes |
companyRevenue | Company Revenue | Current company | number | Yes |
companyFoundedYear | Company Founded Year | Current company | number | Yes |
companyHeadcountGrowth | Company Headcount Growth | Growth and hiring | number | — |
companyDepartmentEmployees | Company Employees by Department | Growth and hiring | list | — |
companyHiringDepartments | Company Hiring Departments | Growth and hiring | list | — |
companyOpenRoles | Company Open Roles | Growth and hiring | list | — |
companyLastFundingType | Company Last Funding Type | Funding | text | — |
companyLastFundingAmount | Company Last Funding Amount | Funding | number | — |
companyTotalFunding | Company Total Funding | Funding | number | — |
companyLastFundingDate | Company Last Funding Date | Funding | date | — |
companyLeadInvestors | Company Lead Investors | Funding | list | — |
companyInvestorCount | Company Investor Count | Funding | number | — |
companyTechnologies | Company Technologies | Technology | list | — |
companyKeywords | Company Keywords | Technology | list | — |
companyTraffic | Company Traffic | Digital signals | number | — |
companyAdvertisingSpend | Company Advertising Spend | Digital signals | number | — |
companyMobileAppPresence | Company Mobile App Presence | Digital signals | boolean | — |
companyWebAppPresence | Company Web App Presence | Digital signals | boolean | — |
companyAppRating | Company App Rating | Digital signals | number | — |
companyAppReviews | Company App Reviews | Digital signals | number | — |
companyIosAppRating | Company iOS App Rating | Digital signals | number | — |
companyGooglePlayAppRating | Company Google Play App Rating | Digital signals | number | — |
companyIosAppReviews | Company iOS App Reviews | Digital signals | number | — |
companyGooglePlayAppReviews | Company Google Play App Reviews | Digital signals | number | — |
companyAppDownloads | Company App Downloads | Digital signals | number | — |
Additional People column mappings
| Export key | CSV value source | Export section |
|---|---|---|
jobLocation | jobLocation.city, .state, .country; never personal or HQ geography | Profile |
educationCompletionYear | All education.endYear values | Experience |
emailLastVerifiedDate | emailLastVerifiedAt | Contact |
contactMethodAvailable | isReachable | Contact |
persona | persona | Profile |
linkedinConnections | linkedinConnections | Profile |
patents | patents | Experience |
publications | publications | Experience |
companyDescription | organization.about, then legacy company description fields | Current company |
Company signal columns and their source fields are listed in Filters and option discovery. Their People equivalents use the organization. prefix and belong to Digital signals. Experience and Digital signals columns follow the account's enabled export-section switches. Request exports.fieldsBySearchType for the exact allowed set before creating an export. Boolean values export as true/false, zero remains 0, absent values remain blank, and lists use | between values. Industry codes retain leading zeros in the CSV text; import those columns as text in spreadsheets.
Company city, state, and country keep their existing primary and legacy values and use the corresponding headquarters value when otherwise blank. Headquarters regions such as APAC are not substituted for a missing State. These fallbacks do not change People location columns or the search's location semantics.
Company keyword filters also search company descriptions. Select the optional companyDescription column in People exports to inspect that text. Missing descriptions remain blank. This field is available through the same UI, API and MCP column catalog and does not change existing export layouts.
App-store metrics
App Rating and App Reviews filters can match either iOS or Google Play. The existing generic columns (appRating, appReviews, and their company-prefixed People equivalents) keep their first-available behavior: iOS is preferred, then Google Play, then legacy sources. A generic value may therefore fall outside your range when the other store matched.
Select iosAppRating, googlePlayAppRating, iosAppReviews, and googlePlayAppReviews for Company exports. People exports use companyIosAppRating, companyGooglePlayAppRating, companyIosAppReviews, and companyGooglePlayAppReviews. These map directly to appStore.iosRating, appStore.playRating, appStore.iosReviews, and appStore.playReviews, with organization. added for People. Missing platform values stay blank; zero remains 0. Only selected columns are added, in your requested order.
Job lifecycle and download
A successful create returns HTTP 202 with job_id and status. Poll api-export-status?job_id=... about every five seconds; polling more frequently does not make jobs complete faster. The status body includes job_id, status, requested_leads, effective_leads, held_credits, final_credits_charged, refunded_credits, download_url, download_expires_at, and error_message; some values remain null until completion.
| Status | Next step |
|---|---|
queued, processing | Continue polling with a bounded timeout. |
completed | GET the non-null download_url to retrieve the file, or use google_sheet_url for a Sheets export. |
failed, cancelled | Stop polling and inspect error_message. Do not assume a usable file exists. |
CSV download URLs are signed and expire; the current in-house URL lifetime is five minutes. Poll status again for a fresh URL if it expires. Treat the URL as a bearer secret. If a create request times out, retry with the same Idempotency-Key and identical body to avoid duplicate jobs. If your polling timeout expires, retain the job ID and resume status polling later.
This Python example uses the standard library. Set SEARCHLEADS_API_KEY, SEARCHLEADS_SEARCH_ID, and SEARCHLEADS_DELIVERY_EMAIL in your environment. It assumes the saved search has at least two available records, creates a two-row CSV with explicitly selected, non-email columns, then downloads it after completion. For a one-result search, intentionally change the row count to 1 before creating the job. This is a real export and consumes credits according to your account policy. The API base below targets the current documentation environment.
import json
import os
import time
import urllib.request
import uuid
from pathlib import Path
base = "https://ttzqldekaizydrdmirpn.supabase.co/functions/v1"
key = os.environ["SEARCHLEADS_API_KEY"]
idempotency_key = str(uuid.uuid4()) # Persist and reuse if create is retried.
def request(path, payload=None, extra_headers=None):
headers = {"x-searchleads-api-key": key, **(extra_headers or {})}
data = None if payload is None else json.dumps(payload).encode()
if data is not None:
headers["Content-Type"] = "application/json"
req = urllib.request.Request(base + path, data=data, headers=headers)
with urllib.request.urlopen(req, timeout=30) as response:
return json.load(response)
job = request("/api-export-create", {
"search_id": os.environ["SEARCHLEADS_SEARCH_ID"],
"requested_leads": 2,
"format": "csv",
"fields": ["name", "title"],
"saveContacts": False,
"delivery_email": os.environ["SEARCHLEADS_DELIVERY_EMAIL"],
}, {"Idempotency-Key": idempotency_key})
job_id = job["job_id"]
for _ in range(120):
state = request("/api-export-status?job_id=" + job_id)
if state["status"] == "completed":
if not state.get("download_url"):
raise RuntimeError("Completed job has no CSV download URL")
with urllib.request.urlopen(state["download_url"], timeout=60) as file:
Path("searchleads-export.csv").write_bytes(file.read())
break
if state["status"] in ("failed", "cancelled"):
raise RuntimeError(state.get("error_message") or state["status"])
time.sleep(5)
else:
raise TimeoutError("Resume polling job " + job_id + " later")The request fields entries and CSV headers use exact keys such as companyHasCio. Display labels such as Company Has CIO are for readability. Read each filter's exportVerification for related CSV keys and verification limitations.
For Company exports with a supported included Company Location filter, location, city, state, and country use the same selected primary or headquarters address as searchLocation in search results. A headquarters match therefore exports headquarters geography even if the main address is in another country. Missing components of that address remain blank; components from different offices are not combined. A CSV shows one address, so ALL across separate locations cannot be fully verified from that tuple alone. Previously generated files are not rewritten.
In the UI, Re-run search restores search criteria. Repeat export restores stored columns, column order, row count and save choice, then opens a review dialog after the search succeeds. It does not submit an export automatically. Current availability, credits and enabled columns are checked again. Older exports without complete stored settings cannot restore those settings; rerun their search and choose new export settings instead.