SolarDialer
Marcador automático con IA: añade y encuentra clientes potenciales, consulta las campañas y haz que el agente de IA llame a un cliente potencial ahora.
Servidor MCP alojado
npx add-mcp 'https://solardialer.net/mcp'Se instala en Claude Code, Codex, Cursor y más
Documentación
Lead API
Send leads straight into your SolarDialer account, and into a list, from your website form, your CRM or your lead seller. One HTTPS request per lead.
Your API key
On your Account page, under API, press Create API key. The key starts with sdk_ and is shown once: copy it then. Keep it secret, like a password: anyone with it can add leads to your account. Replace key gives a new one and stops the old one at once; Turn off stops it.
Each account has its own key, and a key only ever adds leads to its own account.
Send a lead
POST https://solardialer.net/api/v1/leads
Authorization: Bearer sdk_your_key
Content-Type: application/json
{
"first_name": "Dana",
"last_name": "Rivera",
"phone": "215-555-0147",
"state": "PA",
"zip": "19103",
"list": "Website leads"
}
The key can also be sent as X-API-Key: sdk_your_key, and the fields as an ordinary form (application/x-www-form-urlencoded) instead of JSON.
Fields
The same columns as the CSV upload's template, so a lead sent here is checked exactly like an uploaded one. Required: first_name, phone, and for a US number state.
| Field | What it is |
|---|---|
first_name required | The lead's first name: the agent asks for them by it. |
phone required | Their phone number. US numbers in any common form (215-555-0147, (215) 555-0147); other countries with the + code (+44 20 7946 0958). |
lead_id | Your own id for the lead. Send the same id again to update the lead. Without it, the phone number identifies the lead. |
middle_name, last_name | The rest of their name. |
state required for US numbers | Their US state, two letters (PA): it decides the calling hours and rules that apply. |
zip, city, address | Where they live. |
country | Two letters (US, GB …), when the number alone doesn't say. |
second_phone, third_phone | Other numbers for them. Calls ring phone first; when nobody answers, the next call rings the second number, then the third. Once one reaches them, that one is called from then on. A number given twice is kept once. |
utility, source, notes | Their electric company, where the lead came from, anything else. |
best_time_to_call | In words (evenings); or best_call_start and best_call_end as times (17:00, 20:00), in their own time. |
say_name | How to say their name, if it's unusual (Shivon for Siobhan). |
consent_captured_at, consent_source_url, consent_ip, consent_user_agent, consent_disclosure_text, consent_vendor, consent_proof_ref | The record of their consent to be called: when, on which page, from which address, what they agreed to, and your proof (e.g. a TrustedForm or Jornaya id). Send them when you have them. |
list | The name of one of your lists to add the lead to. A new name makes the list. |
Other fields are ignored.
Answers
| Code | Meaning |
|---|---|
201 | A new lead was added. If part of it was left out, warnings says what (e.g. a consent_ip that isn't an IP address: the lead is added without its consent record). |
200 | The lead was already in your account (same lead_id, or the same phone without one): it was updated. |
400 | The lead was refused; errors says why (e.g. a missing first name or state, a number that can't be a phone). |
401 | No API key, or not a valid one. |
403 | The account is suspended. |
429 | More than 1000 leads in an hour: try again later. |
{"ok": true, "id": "u3-W1047", "lead_id": "W1047", "created": true, "list": "Website leads"}
{"ok": false, "errors": ["missing required field: first_name"]}
Examples
curl
curl https://solardialer.net/api/v1/leads \
-H "Authorization: Bearer sdk_your_key" \
-H "Content-Type: application/json" \
-d '{"first_name": "Dana", "phone": "215-555-0147", "state": "PA", "list": "Website leads"}'
JavaScript (your server)
const answer = await fetch("https://solardialer.net/api/v1/leads", {
method: "POST",
headers: {"Authorization": "Bearer " + process.env.SOLARDIALER_KEY,
"Content-Type": "application/json"},
body: JSON.stringify({first_name: "Dana", phone: "215-555-0147", state: "PA"}),
});
console.log(answer.status, await answer.json());
Python
import os, requests
answer = requests.post(
"https://solardialer.net/api/v1/leads",
headers={"Authorization": "Bearer " + os.environ["SOLARDIALER_KEY"]},
json={"first_name": "Dana", "phone": "215-555-0147", "state": "PA"},
)
print(answer.status_code, answer.json())
PHP
<?php
$lead = ["first_name" => "Dana", "phone" => "215-555-0147", "state" => "PA"];
$curl = curl_init("https://solardialer.net/api/v1/leads");
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("SOLARDIALER_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode($lead),
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($curl);
echo curl_getinfo($curl, CURLINFO_RESPONSE_CODE), " ", $body, "\n";
curl_close($curl);
Java (11 or later)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class SendLead {
public static void main(String[] args) throws Exception {
String lead = "{\"first_name\": \"Dana\", \"phone\": \"215-555-0147\", \"state\": \"PA\"}";
HttpRequest request = HttpRequest.newBuilder(URI.create("https://solardialer.net/api/v1/leads"))
.header("Authorization", "Bearer " + System.getenv("SOLARDIALER_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(lead))
.build();
HttpResponse<String> answer = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(answer.statusCode() + " " + answer.body());
}
}
C# (.NET 6 or later)
using System.Net.Http.Headers;
using System.Net.Http.Json;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer", Environment.GetEnvironmentVariable("SOLARDIALER_KEY"));
var lead = new { first_name = "Dana", phone = "215-555-0147", state = "PA" };
var answer = await client.PostAsJsonAsync("https://solardialer.net/api/v1/leads", lead);
Console.WriteLine($"{(int)answer.StatusCode} {await answer.Content.ReadAsStringAsync()}");
Each example reads the key from the SOLARDIALER_KEY environment variable, so it never sits in your code.
Send leads from your server, never from code in a web page: a key in a page's code can be read by anyone who opens it.
Good to know
- One lead per request. A phone number is one lead per account: sending it again updates that lead rather than adding a second.
- Leads are added, not called: call them with the Call button, a campaign, or Call a lead now. Every call still gets the usual checks: the do-not-call list, each lead's calling hours, and the limits on attempts.
- A lead on the do-not-call list is still accepted, and is never called.
- Up to 1000 leads an hour per account.
Call a lead now
Send a new lead (from your web form, Facebook lead ads or a lead seller) and the agent calls it right away, as the Call button would:
POST https://solardialer.net/api/v1/calls
Authorization: Bearer YOUR_KEY
Content-Type: application/json
{"first_name": "Dana", "phone": "215-555-0147", "state": "PA", "list": "Web leads"}
The same fields as Send a lead: the lead is added (or updated), then called. The answer is the lead's, plus called (true or false), call_id and, when it was not called, reason: outside its calling hours, called too recently, on the do-not-call list... The lead stays on its list, so a running campaign on that list calls it when it can. A problem your account must fix first (the agent's script not checked yet, no calls left) answers 409 with what to do. Up to 300 calls an hour.
Optional fields say what the call says, for this call only:
industry: one of your campaign names (the call speaks with that campaign's script, voice and transfer line), or any industry, such as"roofing"or"pool heating"(its script is built for that industry, or yours for it if you set one). It is used even when your account or the lead's campaign has another one. Another name for one of your verticals is read as that one ("AC repair"is HVAC). An industry you have no vertical for yet becomes one of your verticals, with a paused campaign named after it (no leads, so it calls no one by itself): this call speaks with it, the next call naming that industry uses it, and you can edit both in SolarDialer.script: the line your agent says once the lead confirms it's them, up to 400 characters. It is used over everything else.voicemail:trueto leave a message when an answering machine picks up; off unless you send it, even when theindustrycampaign leaves one. With it on, sendvoicemail_message, the text the call's agent says after the beep (theindustrycampaign's agent, else yours, in its voice), up to 400 characters. It must include{company}and{callback_number}(the number the call came from) and may use{first_name},{agent}and{topic}.
Leave them out and the call says what your calls say in SolarDialer, and leaves no voicemail.
{"first_name": "Dana", "phone": "215-555-0147", "industry": "roofing",
"script": "You'd asked about a free roof inspection. Is that still something you want?",
"voicemail": true,
"voicemail_message": "Hi {first_name}, this is {agent} with {company} about your roof inspection. Call us back at {callback_number}."}
A call's recording
GET https://solardialer.net/api/v1/calls/{call_id}/recording
Authorization: Bearer YOUR_KEY
The audio file itself (MP3 or WAV), for any of your calls: the call_id and recording_url in every call event point here. A call that isn't yours, or has no recording, answers 404. Up to 600 an hour. In Zapier, the call triggers give it as a file a step can save to Drive or attach to a CRM record.
Find a lead
GET https://solardialer.net/api/v1/leads?phone=2155550147
GET https://solardialer.net/api/v1/leads?lead_id=W1047
Authorization: Bearer YOUR_KEY
A list with the lead (its lead fields below, plus status and url), or [] when you have none with that number (any of its three) or id.
Events and hooks
What happens on your calls, for Zapier, your CRM or your own server. Each event is JSON with its id, event, created_at and the fields below.
| Event | When | Fields |
|---|---|---|
call.ended | Every call, when it ends | call_id, outcome (talked, voicemail, transferred, transfer_failed, no_answer...), ended_reason, started_at, seconds, campaign_id, campaign, industry (what the call was about: "solar", "HVAC", "roofing"...), disposition, call_url (the call's page), recording_url (the audio, with your key), transcript, summary (a line about the call, when there is one), answers (what the lead answered: homeowner, monthly_bill, roof...), summary (an AI summary of the call in 2-3 sentences), objections (the concerns they raised, short tags such as "too expensive"), interest (1 to 5; these three only when the call reached a person), lead |
call.transferred | A transfer to your closer connected | As call.ended |
lead.interested | The lead said yes to your closer: the transfer connected, did not connect, or your closer's callback was booked instead | As call.ended |
callback.booked | The agent booked a callback | at, when_text (their words), note, set_by, caller (closer: your advisor calls them; agent: the agent calls them again), lead |
lead.status_changed | You gave a lead a status | old, new, lead |
lead.removed | A number went on the do-not-call list | call_id, lead |
lead.created | A lead was added to your account (uploaded, typed in, sent by the API or given on a call); an update of a lead you have sends none | lead, list (its list's name, or null), source (upload, form, dial, api, call, import) |
campaign.finished | A campaign is done with every lead, or was stopped | campaign_id, campaign, how (done or stopped), leads, calls, talked (conversations, transfers included), transferred, appointments, finished_at |
lead is {"lead_id", "first_name", "last_name", "phone", "phones", "email", "address", "city", "state", "zip", "source_id"}; source_id is the id the lead came in with.
Read them
GET https://solardialer.net/api/v1/events?event=call.ended&limit=25
Authorization: Bearer YOUR_KEY
Newest first, up to 100. Leave out event for every kind. To read every one, add after_id (the last id you read; 0 for the start): those after it, oldest first. Ask again from the last one until a page comes back with fewer than limit.
Your transfers and whether they were approved
GET https://solardialer.net/api/v1/transfers?limit=100
Authorization: Bearer YOUR_KEY
Your calls put through to your advisor, newest first, up to 500: call_id, started_at, lead_id, lead_name and approved (true, false, or null while not reviewed).
Have them sent to you (hooks)
POST https://solardialer.net/api/v1/hooks
Authorization: Bearer YOUR_KEY
Content-Type: application/json
{"event": "call.ended", "target_url": "https://example.com/solardialer"}
Answers 201 with the hook's id and secret. From then on each event of that kind is POSTed to your URL (https only), in order, within a minute or so, with the headers X-SolarDialer-Timestamp: <when it was sent, in Unix seconds>, X-SolarDialer-Signature-256: sha256=<HMAC-SHA256 of the timestamp, a dot and the body, with the secret, in hex> and X-SolarDialer-Signature: sha256=<HMAC-SHA256 of the body with the secret, in hex>. Check X-SolarDialer-Signature-256 and refuse a timestamp more than a few minutes old: a post someone captured can't then be sent to you again later (the older header, kept for hooks made before it, carries no time). Answer with any 2xx within 10 seconds. Anything else is tried again after 1, 5 and 30 minutes, 2 and 12 hours, then the hook stops: your Integrations page shows it, and your account's email is told once. Subscribing the same URL to the same event again starts it again, from then on. A 410 removes it.
An event can reach you more than once (an answer lost on the way is a failure, and the event is sent again): use its id to skip one you have already handled.
GET https://solardialer.net/api/v1/hooks/{id}
Authorization: Bearer YOUR_KEY
Whether your hook is still there: 200 with {"id", "event", "target_url", "source", "failing"}, or 404 once it is removed (or isn't yours).
DELETE https://solardialer.net/api/v1/hooks/{id}
Authorization: Bearer YOUR_KEY
Answers 204, also for a hook removed already.
Lists, campaigns, account
GET /api/v1/lists: your lists,[{"id", "name", "leads"}]. Send a lead with"list": "name"to put it on one; it joins that list's campaigns.GET /api/v1/campaigns:[{"id", "name", "status", "list_ids"}]. Send a lead (or call one) with"campaign_id": 5to put it in that campaign: it goes on the campaign's list (a campaign with none gets a list of its own name), over anylistsent. Another account's campaign answers400"That campaign wasn't found."GET /api/v1/me: the key's account,{"id", "name", "email", "company"}.
Every request needs your key. A missing or wrong key gets 401.
MCP server, for AI agents
AI assistants and agent tools that speak the Model Context Protocol (MCP) can use SolarDialer directly: add it as a remote MCP server, and your agent can add leads, look them up, list your campaigns and have your SolarDialer agent call a lead.
- Address:
https://solardialer.net/mcp(Streamable HTTP). - Sign-in: either connect with OAuth (the app sends you to SolarDialer to log in and click Allow: no key to copy), or send your API key as a header:
Authorization: Bearer sdk_.... - Tools:
call_lead_now,add_or_update_lead,find_leadandlist_campaigns, the same as calling a lead now, sending a lead, finding one and your campaigns here, with the same checks and hourly limits.
In claude.ai (Settings → Connectors → Add custom connector), ChatGPT (a custom connector in developer mode) and other apps that connect to remote MCP servers: give the address https://solardialer.net/mcp, then log in to SolarDialer and click Allow when the app sends you there. Account → API lists the apps you connected, each with Disconnect.
In Claude Code, with your API key:
claude mcp add --transport http solardialer https://solardialer.net/mcp --header "Authorization: Bearer sdk_..."
(or without --header, then /mcp in Claude Code to log in with OAuth.)
call_lead_now places a real, billed call, within the lead's calling hours and never to a number on your do-not-call list. Let your agent call only leads you asked it to.