Pre-Black Friday: 20% off, for as long as you stayEARLYBFon Solo and AgencyEnds Nov 28Start free

How-to guide

How to Run 8 Outbound Calling Scripts That Close Deals (with a Phone-Ready List)

A script is only as good as the list behind it. Each script below comes with the filter that builds its call list and the calls that fetch the phone numbers.

Get your free API key →Free to start. No credit card. 1,000 records to spend whenever you like.

Eight outbound calling scripts close deals when each one is paired with the right list. This guide gives the script, the filter that builds its list, and the API calls that return phone numbers. It spends about 12 records for the walkthrough.

The scripts are short on purpose. Reps adapt them. The list does most of the work, because a script read to the wrong person closes nothing.

TL;DR

  • Eight scripts, one per situation. Each maps to a LeadOcean search filter you can size for free before you spend anything.
  • Search rows are thin. Enrich a person to get phone numbers, one record each.
  • Takes about 20 minutes and spends about 12 of the 1,000 free records. Counts cost nothing.

Prerequisites

  • A LeadOcean account. The free plan gives 1,000 records one-off, no card: register here.
  • An API key from the app, exported as $LEADOCEAN_API_KEY.
  • curl and jq.
  • A dialer or a phone. LeadOcean returns data. It has no dialer, sequencer or CRM.
bash
export LEADOCEAN_API_KEY="paste-your-key-here"

Every request goes to https://api.leadocean.io with the header x-api-key.

Step 1: Size the call list for free

Send count=true and limit=1 as query parameters. The call returns a total and spends no record. Filters go in a flat JSON body, and every filter is an array of strings.

bash
curl -s -X POST "https://api.leadocean.io/v1/people/search?count=true&limit=1" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jobLevel": ["VP"],
    "jobFunction": ["Sales & Business Development"],
    "country": ["US"],
    "employeeRange": ["51-200"],
    "hasPhone": true
  }' | jq '.meta'

Enum values are exact strings. VP works, vice president returns nothing. meta.total is capped at 100,000.

A count with hasPhone is a count of people with a phone on file. It is not a mailable count.

Step 2: Pick the script and its filter

Match the situation to a script, then change the filter. Each row below is one search body.

#ScriptWhen to use itFilter changes
1Cold openerNo prior contactjobLevel, jobFunction, employeeRange
2VoicemailNo answer on call 1Same list, second touch
3Trigger callCompany just raisedfundingType, minLastFundingYear
4Objection handlingProspect says "send an email"Same list, emailStatus verified
5Next-step closeProspect is engagedNone, fetch the direct dial
6Tech-stack openerYou replace a known tooltechnologies
7Referral callA champion named a colleaguedomain, seniority
8Re-engagementLost deal, new roleEnrich by linkedin_url

Step 3: Cold opener and voicemail (scripts 1 and 2)

Script 1 is a 20-second opener that names the role and one fact. Script 2 repeats the fact and a callback number, nothing else.

text
SCRIPT 1, cold opener
"Hi {first_name}, this is {rep} at {company}. I'm calling VPs of sales at
50 to 200 person teams. Got 20 seconds to tell you why?"
[If yes] "We help teams like {company_name} {one outcome}. Is that on your list this quarter?"

SCRIPT 2, voicemail
"{first_name}, {rep} at {company}, {number}. I called about {one fact}.
I'll send one line by email. Call me if it helps."

Search the list with the filter from step 1, five rows to start.

bash
curl -s -X POST "https://api.leadocean.io/v1/people/search" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jobLevel": ["VP"],
    "jobFunction": ["Sales & Business Development"],
    "country": ["US"],
    "employeeRange": ["51-200"],
    "hasPhone": true,
    "limit": 5
  }' > page1.json

This spends 5 records. Add "cursor" with the value from meta.nextCursor to page.

Step 4: Trigger and tech-stack calls (scripts 3 and 6)

Script 3 opens on a funding event. Script 6 opens on a tool the company already runs.

text
SCRIPT 3, trigger call
"Congratulations on the round. Teams at your stage usually hire reps next.
Is calling capacity a bottleneck yet?"

SCRIPT 6, tech-stack opener
"I saw your site runs {tool}. Most teams on it hit {one limit}. Does that sound familiar?"

Two bodies, shown as filters only.

json
{"fundingType": ["Series A"], "minLastFundingYear": 2025, "jobLevel": ["C-Team", "VP"], "hasPhone": true}
json
{"technologies": ["HubSpot"], "jobFunction": ["Sales & Business Development"], "hasPhone": true}

Funding filters work in search. Do not rely on them for free counts: the count tool rejects them or returns a fake total.

Step 5: Fetch the phone numbers

Search rows carry a has_phone flag but no number. Each row has an id at meta.source_ids.person_id. Enrich it with reveal_email set to true to get phones: despite the name, that flag returns emails and phone numbers.

bash
PID=$(jq -r '.data[0].meta.source_ids.person_id' page1.json)

curl -s -X POST "https://api.leadocean.io/v1/people/enrich" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"person_id\": \"$PID\", \"reveal_email\": true}" | jq '.data.contact_data.contact_phones'

This costs one record. Enrich takes exactly one of linkedin_url, email, phone or person_id. If we hold nothing for that person you get a 404 and no charge.

Each phone carries phone, type (mobile, direct, office), priority (best first) and dnc. The dnc field is published but not yet filled by the source, so null means unknown, never safe to call. Check your own do-not-call lists before you dial. Dial the lowest priority number first, and prefer mobile or direct over office for scripts 1 and 3.

Step 6: Objection, close, referral and re-engagement (scripts 4, 5, 7, 8)

These four are about the conversation, so the data step is small.

text
SCRIPT 4, "send me an email"
"Happy to. So it lands, is {work_email} still right? What should the subject say?"

SCRIPT 5, next-step close
"Based on what you said, a 25-minute call Thursday or Friday works. Which one?"

SCRIPT 7, referral
"{champion} suggested I speak with you about {topic}. Who owns that on your side?"

SCRIPT 8, re-engagement
"I saw you moved to {new_company}. We spoke at {old_company}. Worth a fresh look?"

For script 4, enrich with "reveal_email": true and check the email status before you promise a send. For script 7, list a company's people with GET /v1/companies/acme.com/people?limit=5. For script 8, enrich by linkedin_url and read the current role from contact_data.contact_current_experiences[0]. Past roles are in resume_data.experiences[] in the same response, with job_title and company_name.

Step 7: Put record fields into the script

A script lands when it names something true about the person. Pull three fields per row and read them before you dial.

bash
jq -r '.data[] | [.profile_data.profile_full_name, .contact_data.contact_current_experiences[0].job_title, .contact_data.contact_current_experiences[0].company_name] | @tsv' page1.json

Use the title to choose the script. A VP of sales hears script 1. A founder at a Series A company hears script 3. A person at a company running a tool you replace hears script 6.

Keep the pre-call check to 30 seconds. Read the title, the company name and one fact, then dial. Anything longer and reps skip the research.

Three rules make the scripts close more often:

  • Ask one question per turn. Stack two and the prospect answers the easier one.
  • Name the next step before you hang up, even on a no. Script 5 does this with two dates.
  • Log the outcome the same day. A call you cannot find a week later is a call you repeat.

Step 8: Run it in batches and measure

Do not dial 49,715 people. Pull 25 rows, call them in one block, and note which script produced a next step.

bash
curl -s -X POST "https://api.leadocean.io/v1/people/search" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jobLevel":["VP"],"jobFunction":["Sales & Business Development"],"country":["US"],"hasPhone":true,"limit":25}' \
  | jq '.meta | {count, nextCursor}'

That spends 25 records. Compare batches of the same size: opener against trigger call, voicemail against none. Change one variable per batch. Five batches of 25 tell you more than one batch of 125 with five changes.

What you get

A free count on 2026-10-21 with the MCP tool leadocean_count_leads: VP, Sales & Business Development, US, 51-200 employees, phone on file.

json
{
  "total": 49715,
  "totalIsExact": true,
  "distinctCompanies": 30002,
  "credits": 0,
  "unsupportedFilters": []
}

That is 49,715 people with a phone on file across 30,002 companies. It is a phone-on-file count, not a mailable count.

A person row looks like this. It is the response shape from the API reference, with placeholder values.

json
{
  "profile_data": { "profile_full_name": "Jane Doe" },
  "contact_data": {
    "has_phone": true,
    "has_email": true,
    "email_status": "verified",
    "contact_current_experiences": [{ "job_title": "VP Sales", "company_name": "Acme", "company_domain": "acme.com" }]
  },
  "meta": { "source_ids": { "person_id": "123456789" } }
}

After the Step 5 enrichment, the same person also carries phones.

json
"contact_phones": [
  { "phone": "+14155550133", "type": "mobile", "priority": 1, "dnc": null }
]

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-keyRe-copy the key from the app. Check $LEADOCEAN_API_KEY is exported.
429Above the plan's rate limit (5 a second on Free, 200 on Agency)Honor the Retry-After header and slow the loop.
402Out of recordsThe free plan's 1,000 are spent. Move to Solo at $97 a month.
Empty resultsBad enum value, such as vice presidentUse VP. List allowed values with GET /v1/enums.
Search stops earlymeta.depthCapped is true at 10,000 rowsNarrow the filters and run smaller searches.
Export refusedOver 50,000 rows a request or 500,000 a daySplit the export. Bulk CSV is not on Free.

FAQ

How long should an outbound calling script be?

Under 30 seconds for the opener. Long scripts get read aloud and sound like it. Keep the structure and let reps change the words.

Do I need a dialer?

Yes. LeadOcean returns the data. Use any dialer or CRM you already run. See the Close stack guide for one setup.

How many records does a call list cost?

One record per person returned. Counts, empty pages and 404s cost nothing. Paid plans run on a fair-use pace, not credits: see pricing.

Is calling better than email?

Use both. A voicemail followed by a short email is a warm outbound pattern. Pick the channel by what the data holds: check email_status before you send.

Where do I compare enrichment tools?

Build your call list before you write the script

Free to start. No credit card. 1,000 records to spend whenever you like.

Get your free API key →