API
Export leads
The bulk path — push leads to Close, GoHighLevel, Sheets, or a downloadable file.
API
The bulk path — push leads to Close, GoHighLevel, Sheets, or a downloadable file.
API
The bulk path — push leads to Close, GoHighLevel, Sheets, or a downloadable file.
POST/api/v1/exports
The bulk path. A single JSON response can’t carry tens of thousands of leads (see Limits), so reading pages at 2,500 and volume goes through an export: a file destination writes the leads to a file and hands back a download link, and a CRM destination pushes the rows straight in.
Export costs nothing — you already paid for these leads.
| destination | Required. close | ghl | google_sheets | csv | xlsx | json. A csv, xlsx or json file is always returned as a download link, valid for 7 days, and is also emailed to you when it is small enough to attach — outcome says which ("CSV emailed to you" or "CSV ready to download"). |
| search_id | Export one search's leads. |
| all | Or set true to export the whole library. |
| filters | Optional filter rows (below), up to 100, with either: within the search when search_id is given, across the library with all=true. |
| spreadsheet_id | google_sheets only: add to a spreadsheet LeadMarina created in an earlier export, updating the leads it already holds. Without it, every call creates a new spreadsheet. |
| cursor | Continue an export: the next_cursor a previous call returned, sent with the same search_id (or all=true) and the same filters. |
curl -X POST https://leadmarina.com/api/v1/exports \
-H "Authorization: Bearer lm_live_xxxx" \
-H "Content-Type: application/json" \
-d '{ "destination": "csv", "all": true }'{
"exported": 12480,
"outcome": "CSV ready to download",
"download_url": "https://…",
"file": "leads-all-leads-2026-08-05.csv",
"note": "A new file was produced. Nothing was matched or de-duplicated against an earlier export.",
"data_sources": "https://leadmarina.com/data-sources"
}Per-call limits differ by destination — 50,000 for CSV/XLSX, 25,000 for JSON, 5,000 for Google Sheets, and 1,000 for Close or GoHighLevel. The CRM figure is low because those APIs take one call per lead, so it’s a time limit rather than a size one; for a large push, export CSV and import the file.
Big exports go in batches. Leads go out best-ranked first (rank, then id — the order of the leads table). If more leads match than the limit, the call sends the first batch and the response adds truncated: true, the limit that applied, a warning and a next_cursor. Call again with the same search_id (or all=true) and filters and cursor set to it for the next batch, until a response comes back without one. A CRM export that runs out of time mid-batch reports not_sent, and its next_cursor sends the rest of that batch. A lead the CRM itself refuses is counted as failed in outcome (“1 failed”); no warning or next_cursor covers it, and a cursor does not offer it again. A cursor sent with other filters, another search or all=true is refused with a 400.
A library export reads the library live. Each call of an all=true export reads the library as it is when that call runs, so a lead that a search running between calls finds again moves in the order, and can be skipped or sent twice. For one consistent copy, export the library in a single call — up to 50,000 leads as CSV or XLSX, 25,000 as JSON — while no search is running.
What updates in place. Close and GoHighLevel update leads exported before, matched on their Google CID, so re-exporting never duplicates them and never overwrites notes or columns you added yourself — and so does google_sheets given the spreadsheet_id of a sheet LeadMarina created earlier. A csv, xlsx or json export, and google_sheets without a spreadsheet_id, is a new file every call: nothing is matched against an earlier export. The response’s note says which happened.
Filter rows. filters is a list of conditions — the same rows MCP’s query_leads takes and a saved view holds. Each condition has:
| field | The field to test, from the list below. |
| operator | A test the field's type takes (below). Case is ignored. |
| value | One value: text, a number (4.5 or "4.5"), or one of a choice field's values. is_empty, is_not_empty, is_true and is_false take none. |
| conj | "and" or "or", joining the condition to the one before it. "and" binds tighter: A and B or C means (A and B) or C. |
{
"destination": "close",
"search_id": "b3e1...c4a",
"filters": [
{ "field": "email_status", "operator": "is", "value": "safe" },
{ "field": "phone_line_type", "operator": "is", "value": "mobile", "conj": "and" }
]
}text fields [operators: contains, not_contains, is, is_not, is_empty, is_not_empty]: name, category, city, state, country, postal_code, address, website, primary_email (or email_1), email_2, email_3, primary_phone (or phone_1), phone_carrier (or phone_1_carrier), phone_2, phone_2_carrier, phone_3, phone_3_carrier, owner_name, contact_name, contact_title, social_facebook, social_instagram, social_linkedin, social_x, social_youtube, social_tiktok, social_yelp, gbp_url, google_cid, logo_url, main_image_url. number fields [operators: eq, neq, gt, gte, lt, lte, is_empty, is_not_empty]: rank, rating, reviews, people. yes/no fields [operators: is_true, is_false, is_empty, is_not_empty]: claimed. choice fields [operators: is, is_not, is_empty, is_not_empty]: website_status take none (No website), live, unreachable, blocked (Blocked our crawler), unknown, pending; email_status (or email_1_status), email_2_status, email_3_status take safe, risky, invalid, unknown; phone_line_type (or phone_1_type), phone_2_type, phone_3_type take mobile, fixed_line (Landline), fixed_line_or_mobile (Landline or mobile), voip, non_fixed_voip, toll_free (Toll-free), premium_rate, shared_cost, personal_number (Personal), pager, uan, voicemail, unknown.
Text matching ignores case. Phone fields compare digits, so “(512) 555” finds +15125550100. Website is / is_not compare domains when given a domain (“acme.com”, or “https://www.acme.com/”), and whole addresses when given a path, a query or a fragment (“acme.com/locations/austin”) — either way ignoring http(s)://, www. and a closing slash. A field, operator or value the API can’t use is refused with a 400 that names it and says what works — and nothing is sent.
Errors. 400 when the request can’t run as sent — a body that isn’t a JSON object, an unknown destination, a filter or cursor the API can’t use, no matching leads — or when the destination isn’t ready: not connected, connection expired or no longer accepted by the destination, Close’s field mapping not saved yet or out of date, or a spreadsheet LeadMarina didn’t create. The message says what to do. 404 for a search that doesn’t exist or isn’t yours. 503 with Retry-After when our database was busy before the first lead was sent: nothing reached the destination, and the same call works shortly. Any other failure of the export is a 500 with a ref to quote to support, and its message says whether any leads reached the destination first — “Nothing was sent” when none did.