# Lead sources

> Import from Apollo, Instantly and Smartlead with your own key, or take rows from Clay. 5 Ballast REST endpoints with parameters, permissions, the MCP tool and a curl example.

Canonical: https://ballast.to/docs/api/lead-sources

[API reference](https://ballast.to/docs/api)

Import from Apollo, Instantly and Smartlead with your own key, or take rows from Clay. 5 endpoints, each also a tool for your AI through [MCP](https://ballast.to/docs/mcp). Authentication and errors are on the [overview](https://ballast.to/docs/api#authentication).

## List lead sources and whether each is connected

GET

/api/v1/lead-sources

Apollo, Instantly, Smartlead and Lemlist are pulled with the workspace's own API key (connected in the dashboard, Integrations). Clay pushes rows to the workspace's own URL (shown to owners and admins in the dashboard) or calls POST /leads/import. Keys and the Clay URL are never returned.

MCP tool

list\_lead\_sources

Read-only

Returns

- **`sources` required object\[\]**

cURL

```
curl https://ballast.to/api/v1/lead-sources \
  -H "Authorization: Bearer $BALLAST_API_KEY"
```

## Test a lead source's key again

POST

/api/v1/lead-sources/{provider}/check

Same free calls as on connect; a refused key marks the source Needs attention.

MCP tool

check\_lead\_source

Owners and admins

Parameters

- **`provider` required path · "apollo" | "instantly" | "smartlead" | "lemlist"**

Returns

- **`provider` required "apollo" | "instantly" | "smartlead" | "lemlist" | "clay"**
- **`connected` required boolean**
- **`status` required "connected" | "error" | null**
- **`lastError` required string | null**
- **`lastCheckedAt` required string | null**
- **`keyHint` required string | null**: Last 4 characters of the API key (Apollo, Instantly, Smartlead)
- **`connectedBy` required string | null**
- **`account` required object | null**: What the key could see at the last check: lists, campaigns, saved contacts (Apollo)
- **`clay` required object | null**: Clay only: where pushed rows go, and the last push

cURL

```
curl -X POST https://ballast.to/api/v1/lead-sources/<provider>/check \
  -H "Authorization: Bearer $BALLAST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
```

## Lists and campaigns you can import from a lead source

GET

/api/v1/lead-sources/{provider}/lists

Read live from the connected account. Apollo: contact lists (with counts) and all saved contacts. Instantly: lead lists and campaigns. Smartlead and Lemlist: campaigns.

MCP tool

list\_lead\_source\_lists

Read-only

Parameters

- **`provider` required path · "apollo" | "instantly" | "smartlead" | "lemlist"**

Returns

- **`items` required object\[\]**

cURL

```
curl https://ballast.to/api/v1/lead-sources/<provider>/lists \
  -H "Authorization: Bearer $BALLAST_API_KEY"
```

## Import a list or campaign from Apollo, Instantly, Smartlead or Lemlist

POST

/api/v1/lead-sources/{provider}/import

Starts an import that runs in the background, through the same checks as a file: addresses deduped, suppressed ones skipped, invalid ones counted, the plan's contact limit applied. Leads that unsubscribed or bounced there become suppressions. Follow it with get\_import (status importing, then enriching, then done).

MCP tool

import\_from\_lead\_source

Needs an active plan

MCP asks to confirm

Parameters

- **`provider` required path · "apollo" | "instantly" | "smartlead" | "lemlist"**
- **`kind` required "list" | "campaign" | "all"**
- **`id` required string**: The list or campaign id from list\_lead\_source\_lists
- **`list` object | null**: List the leads go to: an existing list id or a new list's name; null or left out = no list
- **`existing` "skip" | "update"**: Leads already in the workspace: leave them as they are, or fill in the new values
- **`verify` boolean**: Verify the new addresses with the connected verifier after import

Returns

- **`id` required string**
- **`fileName` required string | null**
- **`listId` required string | null**
- **`listName` required string | null**
- **`status` required "importing" | "enriching" | "done" | "failed"**
- **`verify` required boolean**
- **`rows` required number**
- **`created` required number**
- **`updated` required number**
- **`skipped` required number**
- **`duplicates` required number**
- **`suppressed` required number**
- **`invalid` required number**
- **`undeliverable` required number**
- **`createdAt` required string**
- **`finishedAt` required string | null**
- **`enrichedAt` required string | null**
- **`source` required "apollo" | "instantly" | "smartlead" | "lemlist" | "clay" | null**: Null for a file or API import; apollo, instantly or smartlead when pulled from that account; clay for rows Clay pushed
- **`sourceName` required string | null**: The list or campaign it came from
- **`expected` required number | null**: Leads the source said the list holds, when it says (progress while pulling)
- **`error` required string | null**: Why an import from a source stopped before the end

cURL

```
curl -X POST https://ballast.to/api/v1/lead-sources/<provider>/import \
  -H "Authorization: Bearer $BALLAST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"kind":"list","id":"<id>"}'
```

## Where rows Clay pushes go

PUT

/api/v1/lead-sources/clay

The list, what happens to leads already in the workspace, and verification. Clay must be turned on in the dashboard first.

MCP tool

update\_clay\_settings

Owners and admins

Parameters

- **`list` object | null**: List the leads go to: an existing list id or a new list's name; null or left out = no list
- **`existing` "skip" | "update"**: A pushed lead already in the workspace: leave it, or fill in the new values
- **`verify` boolean**: Verify new addresses with the connected verifier

Returns the same fields as Test a lead source's key again.

cURL

```
curl -X PUT https://ballast.to/api/v1/lead-sources/clay \
  -H "Authorization: Bearer $BALLAST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"list":{},"existing":"skip"}'
```
