Connect Account
curl --request POST \
--url https://api.linkupapi.com/v2/login \
--header 'Content-Type: application/json' \
--header 'x-api-key: <x-api-key>' \
--data '
{
"platform": "<string>",
"email": "<string>",
"password": "<string>",
"login_token": "<string>",
"phone_number": "<string>",
"country": "<string>",
"account_name": "<string>",
"challenge_type": "<string>",
"sales_nav": true,
"account_id": "<string>",
"proxy": {}
}
'import requests
url = "https://api.linkupapi.com/v2/login"
payload = {
"platform": "<string>",
"email": "<string>",
"password": "<string>",
"login_token": "<string>",
"phone_number": "<string>",
"country": "<string>",
"account_name": "<string>",
"challenge_type": "<string>",
"sales_nav": True,
"account_id": "<string>",
"proxy": {}
}
headers = {
"x-api-key": "<x-api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<x-api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
platform: '<string>',
email: '<string>',
password: '<string>',
login_token: '<string>',
phone_number: '<string>',
country: '<string>',
account_name: '<string>',
challenge_type: '<string>',
sales_nav: true,
account_id: '<string>',
proxy: {}
})
};
fetch('https://api.linkupapi.com/v2/login', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.linkupapi.com/v2/login",
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([
'platform' => '<string>',
'email' => '<string>',
'password' => '<string>',
'login_token' => '<string>',
'phone_number' => '<string>',
'country' => '<string>',
'account_name' => '<string>',
'challenge_type' => '<string>',
'sales_nav' => true,
'account_id' => '<string>',
'proxy' => [
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <x-api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.linkupapi.com/v2/login"
payload := strings.NewReader("{\n \"platform\": \"<string>\",\n \"email\": \"<string>\",\n \"password\": \"<string>\",\n \"login_token\": \"<string>\",\n \"phone_number\": \"<string>\",\n \"country\": \"<string>\",\n \"account_name\": \"<string>\",\n \"challenge_type\": \"<string>\",\n \"sales_nav\": true,\n \"account_id\": \"<string>\",\n \"proxy\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<x-api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.linkupapi.com/v2/login")
.header("x-api-key", "<x-api-key>")
.header("Content-Type", "application/json")
.body("{\n \"platform\": \"<string>\",\n \"email\": \"<string>\",\n \"password\": \"<string>\",\n \"login_token\": \"<string>\",\n \"phone_number\": \"<string>\",\n \"country\": \"<string>\",\n \"account_name\": \"<string>\",\n \"challenge_type\": \"<string>\",\n \"sales_nav\": true,\n \"account_id\": \"<string>\",\n \"proxy\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.linkupapi.com/v2/login")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<x-api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"platform\": \"<string>\",\n \"email\": \"<string>\",\n \"password\": \"<string>\",\n \"login_token\": \"<string>\",\n \"phone_number\": \"<string>\",\n \"country\": \"<string>\",\n \"account_name\": \"<string>\",\n \"challenge_type\": \"<string>\",\n \"sales_nav\": true,\n \"account_id\": \"<string>\",\n \"proxy\": {}\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"data": {
"account_id": "69c127c37cae0494dd827286",
"platform": "linkedin",
"status": "connected"
},
"metadata": {
"action": "login",
"credits_consumed": 1,
"timestamp": "2025-01-15T10:30:00.000000"
}
}
{
"success": true,
"data": {
"account_id": "69c127c37cae0494dd827286",
"platform": "linkedin",
"status": "checkpoint_required",
"message": "Check your email for verification code",
"checkpoint_type": "sms",
"sent_to": "phone number ending with 23"
},
"metadata": {
"action": "login",
"credits_consumed": 1,
"timestamp": "2025-01-15T10:30:00.000000"
}
}
{
"success": true,
"data": {
"account_id": "6ab7dd7ba4886df610eab269",
"platform": "whatsapp",
"status": "checkpoint_required",
"checkpoint_type": "whatsapp_qr",
"qr_code": "https://wa.me/settings/linked_devices#2@…",
"qr_code_image": "data:image/png;base64,iVBORw0KGgo…",
"expires_in_seconds": 180
},
"metadata": {
"action": "login",
"credits_consumed": 0,
"timestamp": "2026-09-26T15:00:00.000000"
}
}
{
"success": false,
"error": {
"code": "CHANNEL_ERROR",
"message": "Bad username or password"
},
"metadata": {
"action": "login",
"credits_consumed": 0,
"timestamp": "2025-01-15T10:30:00.000000"
}
}
Accounts
Connect Account
Connect a channel account to start using the API
POST
/
v2
/
login
Connect Account
curl --request POST \
--url https://api.linkupapi.com/v2/login \
--header 'Content-Type: application/json' \
--header 'x-api-key: <x-api-key>' \
--data '
{
"platform": "<string>",
"email": "<string>",
"password": "<string>",
"login_token": "<string>",
"phone_number": "<string>",
"country": "<string>",
"account_name": "<string>",
"challenge_type": "<string>",
"sales_nav": true,
"account_id": "<string>",
"proxy": {}
}
'import requests
url = "https://api.linkupapi.com/v2/login"
payload = {
"platform": "<string>",
"email": "<string>",
"password": "<string>",
"login_token": "<string>",
"phone_number": "<string>",
"country": "<string>",
"account_name": "<string>",
"challenge_type": "<string>",
"sales_nav": True,
"account_id": "<string>",
"proxy": {}
}
headers = {
"x-api-key": "<x-api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<x-api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
platform: '<string>',
email: '<string>',
password: '<string>',
login_token: '<string>',
phone_number: '<string>',
country: '<string>',
account_name: '<string>',
challenge_type: '<string>',
sales_nav: true,
account_id: '<string>',
proxy: {}
})
};
fetch('https://api.linkupapi.com/v2/login', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.linkupapi.com/v2/login",
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([
'platform' => '<string>',
'email' => '<string>',
'password' => '<string>',
'login_token' => '<string>',
'phone_number' => '<string>',
'country' => '<string>',
'account_name' => '<string>',
'challenge_type' => '<string>',
'sales_nav' => true,
'account_id' => '<string>',
'proxy' => [
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <x-api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.linkupapi.com/v2/login"
payload := strings.NewReader("{\n \"platform\": \"<string>\",\n \"email\": \"<string>\",\n \"password\": \"<string>\",\n \"login_token\": \"<string>\",\n \"phone_number\": \"<string>\",\n \"country\": \"<string>\",\n \"account_name\": \"<string>\",\n \"challenge_type\": \"<string>\",\n \"sales_nav\": true,\n \"account_id\": \"<string>\",\n \"proxy\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<x-api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.linkupapi.com/v2/login")
.header("x-api-key", "<x-api-key>")
.header("Content-Type", "application/json")
.body("{\n \"platform\": \"<string>\",\n \"email\": \"<string>\",\n \"password\": \"<string>\",\n \"login_token\": \"<string>\",\n \"phone_number\": \"<string>\",\n \"country\": \"<string>\",\n \"account_name\": \"<string>\",\n \"challenge_type\": \"<string>\",\n \"sales_nav\": true,\n \"account_id\": \"<string>\",\n \"proxy\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.linkupapi.com/v2/login")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<x-api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"platform\": \"<string>\",\n \"email\": \"<string>\",\n \"password\": \"<string>\",\n \"login_token\": \"<string>\",\n \"phone_number\": \"<string>\",\n \"country\": \"<string>\",\n \"account_name\": \"<string>\",\n \"challenge_type\": \"<string>\",\n \"sales_nav\": true,\n \"account_id\": \"<string>\",\n \"proxy\": {}\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"data": {
"account_id": "69c127c37cae0494dd827286",
"platform": "linkedin",
"status": "connected"
},
"metadata": {
"action": "login",
"credits_consumed": 1,
"timestamp": "2025-01-15T10:30:00.000000"
}
}
{
"success": true,
"data": {
"account_id": "69c127c37cae0494dd827286",
"platform": "linkedin",
"status": "checkpoint_required",
"message": "Check your email for verification code",
"checkpoint_type": "sms",
"sent_to": "phone number ending with 23"
},
"metadata": {
"action": "login",
"credits_consumed": 1,
"timestamp": "2025-01-15T10:30:00.000000"
}
}
{
"success": true,
"data": {
"account_id": "6ab7dd7ba4886df610eab269",
"platform": "whatsapp",
"status": "checkpoint_required",
"checkpoint_type": "whatsapp_qr",
"qr_code": "https://wa.me/settings/linked_devices#2@…",
"qr_code_image": "data:image/png;base64,iVBORw0KGgo…",
"expires_in_seconds": 180
},
"metadata": {
"action": "login",
"credits_consumed": 0,
"timestamp": "2026-09-26T15:00:00.000000"
}
}
{
"success": false,
"error": {
"code": "CHANNEL_ERROR",
"message": "Bad username or password"
},
"metadata": {
"action": "login",
"credits_consumed": 0,
"timestamp": "2025-01-15T10:30:00.000000"
}
}
Connect a new channel account (LinkedIn, WhatsApp, etc.) to your API key. Once connected, you receive an
Common failures return the standard error envelope: an address with no Gmail mailbox or a rejected SMTP login → WhatsApp
account_id to use in all subsequent requests.
This endpoint costs 1 credit per request. Ignore this if you’re on per-seat pricing: usage is unlimited.
Header Parameters
string
required
Your API key
Body Parameters
string
required
The channel platform to connect. Available:
linkedin, whatsapp, email. Optional in the one case below where you pass an existing account_id to activate a premium seat — the account already carries its platform. For email, see Email mailbox below.string
Account email address (required for credential-based login)
string
Account password (required for credential-based login)
string
Authentication token/cookie for direct token-based connection. Alternative to email+password.
string
WhatsApp only. The number to link, e.g.
+33612345678. Sets the proxy country when country is not given.string
default:"FR"
Country code for proxy selection. Available: US, UK, FR, DE, NL, IT, IL, CA, BR, ES, IN
string
Optional display name for the account. Defaults to the email address.
string
default:"code_challenge"
Type of 2FA challenge to use if the account has two-factor authentication enabled.
code_challenge— receive a verification code via email, SMS, or authenticator app. You must then submit the code via/v2/checkpoint.app_challenge— confirm the login directly from the platform’s mobile app. No code needed — just call/v2/checkpointwith theaccount_idafter approving on the app.
checkpoint_type in the response.boolean
default:false
Also activate the account’s Sales Nav seat during login (requires the account to hold a Sales Nav license). No extra credential is needed — the seat is derived from the existing session. See Sales Nav below.
string
Pass an existing account’s id together with
sales_nav: true to activate a Sales Nav seat on an already-connected account (e.g. bought after connecting) — no re-login, no credential, and no credit. When set, no other login parameter is required.string | object
LinkedIn only. Your own HTTP(S) proxy for this account — the login and every later request go through it. See Custom proxy below.
Response
boolean
Whether the request was successful
object
Show Properties
Show Properties
string
The unique identifier for the connected account. Use this in all future requests.
string
The platform that was connected
string
Account status:
connected (ready to use) or checkpoint_required (needs 2FA verification)string
Additional information (present when checkpoint is required)
string
Where the code went, when
checkpoint_required: email, sms, authenticator_app, app_approval or code.string
Masked destination when LinkedIn shows it, e.g.
phone number ending with 23.object
Present when
sales_nav: true was requested. { "connected": true, "contract_id": "...", "seat_id": "..." } on success, or { "connected": false, "error": "..." } if no seat could be activated. When the login returns checkpoint_required, this is { "pending": true } and the seat is activated once the checkpoint completes.object
Present when
proxy was given: { "mode": "custom", "host": "...", "port": 8080 }.{
"success": true,
"data": {
"account_id": "69c127c37cae0494dd827286",
"platform": "linkedin",
"status": "connected"
},
"metadata": {
"action": "login",
"credits_consumed": 1,
"timestamp": "2025-01-15T10:30:00.000000"
}
}
{
"success": true,
"data": {
"account_id": "69c127c37cae0494dd827286",
"platform": "linkedin",
"status": "checkpoint_required",
"message": "Check your email for verification code",
"checkpoint_type": "sms",
"sent_to": "phone number ending with 23"
},
"metadata": {
"action": "login",
"credits_consumed": 1,
"timestamp": "2025-01-15T10:30:00.000000"
}
}
{
"success": true,
"data": {
"account_id": "6ab7dd7ba4886df610eab269",
"platform": "whatsapp",
"status": "checkpoint_required",
"checkpoint_type": "whatsapp_qr",
"qr_code": "https://wa.me/settings/linked_devices#2@…",
"qr_code_image": "data:image/png;base64,iVBORw0KGgo…",
"expires_in_seconds": 180
},
"metadata": {
"action": "login",
"credits_consumed": 0,
"timestamp": "2026-09-26T15:00:00.000000"
}
}
{
"success": false,
"error": {
"code": "CHANNEL_ERROR",
"message": "Bad username or password"
},
"metadata": {
"action": "login",
"credits_consumed": 0,
"timestamp": "2025-01-15T10:30:00.000000"
}
}
Login Methods
There are two ways to connect an account:-
Credential-based login — provide
email+password. If 2FA is enabled, you’ll receive acheckpoint_requiredstatus and need to call/v2/checkpoint. -
Token-based login — provide a
login_tokendirectly. No 2FA step needed.
Email mailbox
Connect a mailbox withplatform: "email" and a provider:
gmail_oauth/m365_oauth— OAuth. Two steps: this call returnsstatus: "checkpoint_required"+ anauthorization_url; redirect the user there, then submit the returnedcodeto/v2/checkpoint. By default LinkupAPI’s own OAuth app is used — passoauth_client_id+oauth_client_secretinparamsto use your own.smtp_generic— IMAP/SMTP. Verified and connected in one call (no checkpoint).
email field (the same one used for credential login), and its display name the top-level account_name. Everything else goes in params: optional daily_limit (default 40); redirect_uri for OAuth; smtp { host, port, password } (+ optional imap { host, port }) for SMTP. Tip: call /v2/email/autoconfig first to get the right provider and server settings. Connecting a mailbox is free.
OAuth (step 1)
{
"platform": "email",
"provider": "gmail_oauth",
"email": "you@company.com",
"params": { "redirect_uri": "https://yourapp.com/oauth/email/callback" }
}
SMTP (one call)
{
"platform": "email",
"provider": "smtp_generic",
"email": "you@company.com",
"params": {
"smtp": { "host": "smtp.company.com", "port": 587, "password": "app-password" },
"imap": { "host": "imap.company.com", "port": 993 }
}
}
CHANNEL_ERROR (422); an expired/reused code → INVALID_PARAMS (400); denied consent → FORBIDDEN (403). A failed attempt leaves no leftover account.
platform: "whatsapp"+phone_number→checkpoint_requiredwith a QR code (qr_code_image)- Scan it on the phone: WhatsApp › Settings › Linked devices › Link a device
- Poll
/v2/checkpointwith theaccount_idevery few seconds → current QR, thenconnected
- The QR refreshes every ~20 s and expires after 3 min:
/v2/checkpointwithresend: truegives a new one - Can’t scan (QR shown on the same phone)? Pass
params.pairing_code: true: you get an 8-characterpairing_codeto type on the phone instead (Link a device › Link with phone number instead) - Free
WhatsApp
{ "platform": "whatsapp", "phone_number": "+33612345678" }
Sales Nav
The Sales Nav seat is derived from the account’s existing session — there is no separate login. There are two ways to activate it:- At login — pass
sales_nav: truealongside your credentials orlogin_token. The seat is activated in the same request. - On an already-connected account — pass
account_id+sales_nav: true(no credential, no credit). Ideal when the user buys Sales Nav after connecting.
status: connected with sales_nav.connected: false.
{
"success": true,
"data": {
"account_id": "69c127c37cae0494dd827286",
"platform": "linkedin",
"status": "connected",
"sales_nav": {
"connected": true,
"contract_id": "2016220713",
"seat_id": "1545550052"
}
},
"metadata": {
"action": "login",
"credits_consumed": 1,
"timestamp": "2025-01-15T10:30:00.000000"
}
}
{
"success": true,
"data": {
"account_id": "69c127c37cae0494dd827286",
"platform": "linkedin",
"status": "connected",
"sales_nav": { "connected": true, "contract_id": "2016220713", "seat_id": "1545550052" }
},
"metadata": {
"action": "login",
"credits_consumed": 0,
"timestamp": "2025-01-15T10:30:00.000000"
}
}
Custom proxy
proxy:http://user:pass@host:port,host:port:user:pass,host:port, or{ "host", "port", "username", "password" }. HTTP(S) only- tested before the login — unreachable proxy returns
400, no credit - kept for later re-logins. Change it with Set Custom Proxy, remove it with Remove Custom Proxy
Notes
- The
account_idreturned is permanent — store it and reuse it for all API calls - If you receive
checkpoint_required, use the/v2/checkpointendpoint to complete verification - The
countryparameter determines which proxy is used for the connection
Was this page helpful?