curl --request POST \
--url https://api.lemlist.com/api/calls \
--header 'Authorization: Basic <encoded-value>' \
--header 'Content-Type: application/json' \
--data '
{
"externalId": "trellus-8f2c1",
"userId": "usr_3Bv7GhT5nJk8Lm2Qx",
"direction": "outbound",
"startedAt": "2026-10-21T09:30:00Z"
}
'import requests
url = "https://api.lemlist.com/api/calls"
payload = {
"externalId": "trellus-8f2c1",
"userId": "usr_3Bv7GhT5nJk8Lm2Qx",
"direction": "outbound",
"startedAt": "2026-10-21T09:30:00Z"
}
headers = {
"Authorization": "Basic <encoded-value>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Basic <encoded-value>', 'Content-Type': 'application/json'},
body: JSON.stringify({
externalId: 'trellus-8f2c1',
userId: 'usr_3Bv7GhT5nJk8Lm2Qx',
direction: 'outbound',
startedAt: '2026-10-21T09:30:00Z'
})
};
fetch('https://api.lemlist.com/api/calls', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));require 'uri'
require 'net/http'
url = URI("https://api.lemlist.com/api/calls")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Basic <encoded-value>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"externalId\": \"trellus-8f2c1\",\n \"userId\": \"usr_3Bv7GhT5nJk8Lm2Qx\",\n \"direction\": \"outbound\",\n \"startedAt\": \"2026-10-21T09:30:00Z\"\n}"
response = http.request(request)
puts response.read_body<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.lemlist.com/api/calls",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'externalId' => 'trellus-8f2c1',
'userId' => 'usr_3Bv7GhT5nJk8Lm2Qx',
'direction' => 'outbound',
'startedAt' => '2026-10-21T09:30:00Z'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Basic <encoded-value>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}Log a Call
Logs a call made outside lemlist, from an external dialer, with its status, note, recording and transcript.
curl --request POST \
--url https://api.lemlist.com/api/calls \
--header 'Authorization: Basic <encoded-value>' \
--header 'Content-Type: application/json' \
--data '
{
"externalId": "trellus-8f2c1",
"userId": "usr_3Bv7GhT5nJk8Lm2Qx",
"direction": "outbound",
"startedAt": "2026-10-21T09:30:00Z"
}
'import requests
url = "https://api.lemlist.com/api/calls"
payload = {
"externalId": "trellus-8f2c1",
"userId": "usr_3Bv7GhT5nJk8Lm2Qx",
"direction": "outbound",
"startedAt": "2026-10-21T09:30:00Z"
}
headers = {
"Authorization": "Basic <encoded-value>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Basic <encoded-value>', 'Content-Type': 'application/json'},
body: JSON.stringify({
externalId: 'trellus-8f2c1',
userId: 'usr_3Bv7GhT5nJk8Lm2Qx',
direction: 'outbound',
startedAt: '2026-10-21T09:30:00Z'
})
};
fetch('https://api.lemlist.com/api/calls', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));require 'uri'
require 'net/http'
url = URI("https://api.lemlist.com/api/calls")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Basic <encoded-value>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"externalId\": \"trellus-8f2c1\",\n \"userId\": \"usr_3Bv7GhT5nJk8Lm2Qx\",\n \"direction\": \"outbound\",\n \"startedAt\": \"2026-10-21T09:30:00Z\"\n}"
response = http.request(request)
puts response.read_body<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.lemlist.com/api/calls",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'externalId' => 'trellus-8f2c1',
'userId' => 'usr_3Bv7GhT5nJk8Lm2Qx',
'direction' => 'outbound',
'startedAt' => '2026-10-21T09:30:00Z'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Basic <encoded-value>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}- Map your outcomes to the team’s call statuses once, from
GET /call-statuses. - When a call ends, log it with your own
externalId, the rep’suserId, the numbers (from,to), and the task it was made for (taskId, fromGET /tasks) or the contact it was with. - When the recording or the transcript lands, send it with
PATCH /calls/{id}. - Read the call back with
GET /calls/{id}when you need its state,hasRecordingfirst.
externalId is never logged twice.
Send your dialer’s name in provider (for example trellus) on every call: lemlist keeps it to count calls by dialer, while phoneProvider stays api.
What the status does
The automations a call runs come from the status the team configured, not from the status key. lemlist’s default statuses,connected-positive included, only count the call as answered: a status that marks the lead interested or pauses its campaigns is one the team set up so in Settings → Calls. Read the callPositive, callNegative and pausesCampaigns flags of each key from GET /call-statuses before mapping your outcomes, and expect no effect a flag does not announce.
Task or lead
With ataskId, the call completes that task and the sequence moves on, as when a rep marks it done from the dialer. To log a call on a lead without touching its tasks, send leadId (or contactId) instead: the call lands in the timeline and the CRM, the task stays open.
The numbers
from and to take what your telephony reported. A value that reads as a phone number is standardized to E.164 as the contact panel does: its own country code first, else the team’s billing country for a national number. Anything else is kept as sent and never refuses the call: an anonymous caller, an extension, a test number that is no real number such as +15555555555. Only an E.164 finds a contact when to (or from on an inbound call) is the only reference you send.
What the answer guarantees
A201 (or a 200 on a replay) means the call is stored. Everything that follows runs right after it and is alerted to lemlist when it fails, never answered to you: the push to the CRM, the completion of the task, the status automations, the copy of the recording, the storage of the transcript. A retry answers the stored call and runs none of them again.
What you can observe and repair from the API:
hasRecordingstaysfalsewhen the recording could not be fetched (an expired or protected URL): send thePATCHagain with a reachable URL.- The note and the status can be sent again with a
PATCH; both reach the CRM call. - A call missing from the CRM is on lemlist’s side: lemlist is alerted and looks at it.
Authorizations
Basic authentication header of the form Basic <encoded-value>, where <encoded-value> is the base64-encoded string username:password.
Body
Your own id of the call, unique in your dialer. Letters, digits, ., _, : and -, 128 characters at most. The idempotency key: the same id never logs twice, and PATCH /calls/{id} accepts it.
128^[A-Za-z0-9][A-Za-z0-9._:-]*$"trellus-8f2c1"
The lemlist user who made or took the call (usr_…), a member of the team.
"usr_3Bv7GhT5nJk8Lm2Qx"
inbound, outbound "outbound"
When the call started, ISO 8601. May be in the past; at most 5 minutes in the future.
"2026-10-21T09:30:00Z"
The caller's number: the rep's line on an outbound call, the contact's number on an inbound one. Standardized to E.164 as the contact panel does (its own country code, else the team's billing country for a national number) and kept as sent otherwise: an anonymous caller, an extension or a test number that is no real number (+15555555555) never refuses the call. At most 32 characters.
"+33100000000"
The number called: the contact's number on an outbound call, the rep's line on an inbound one, standardized like from. On an outbound call its E.164 also finds the contact when no other reference is sent.
"+33612345678"
Seconds.
0 <= x <= 8640090
A key of the team's call statuses (GET /call-statuses). Runs the automations that status carries, as the team configured them: lemlist's default statuses, connected-positive included, only count the call as answered; marking the lead interested or pausing its campaigns comes with a status the team set up so, read its callPositive and pausesCampaigns flags.
"connected-positive"
The rep's note on the call: shown with the call in lemlist and written in the CRM call note.
10000"Asked for a demo next week."
An https URL of the recording, reachable from the public internet. Fetched in the background. An empty string means no recording.
2048"https://recordings.dialer.example/8f2c1.mp3"
The transcript as plain text.
200000"Hi Jane, this is Alex from lemlist…"
Your dialer's name, the same on every call you log. lemlist stores it lowercase, apart from phoneProvider (which stays api), to count calls by dialer. An empty string means none.
64"trellus"
A call task of a campaign (opp_…, from GET /tasks): the call completes it and the sequence moves on. The task's contact and lead are the call's. To log a call on a lead without completing a task, send leadId or contactId instead.
"opp_9Xw4RtY7kLm2Pq6Zc"
The lead the call was with (lea_…), with its campaign.
"lea_2Hn8VbQ4wRz6Ym3Jk"
The contact the call was with (ctc_…).
"ctc_7Ks2PqW9mXb4Lc1Td"
An email address of the contact the call was with.
"jane@acme.io"
A phone number the call was with: the contact holding it in its phone field. Read as the contact panel reads a number: its own country code, else the team's billing country for a national number. A value that is no number names nobody (CALLS_API_CONTACT_NOT_FOUND).
"+33612345678"
Was this page helpful?