curl --request POST \
--url https://api.datalegion.ai/person/search \
--header 'API-Key: <api-key>' \
--header 'Content-Type: application/json' \
--data @- <<EOF
{
"query": "SELECT * FROM people WHERE city = 'San Francisco' ORDER BY last_seen DESC",
"limit": 10,
"titlecase": false
}
EOFimport requests
url = "https://api.datalegion.ai/person/search"
payload = {
"query": "SELECT * FROM people WHERE city = 'San Francisco' ORDER BY last_seen DESC",
"limit": 10,
"titlecase": False
}
headers = {
"API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
query: 'SELECT * FROM people WHERE city = \'San Francisco\' ORDER BY last_seen DESC',
limit: 10,
titlecase: false
})
};
fetch('https://api.datalegion.ai/person/search', 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.datalegion.ai/person/search",
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([
'query' => 'SELECT * FROM people WHERE city = \'San Francisco\' ORDER BY last_seen DESC',
'limit' => 10,
'titlecase' => false
]),
CURLOPT_HTTPHEADER => [
"API-Key: <api-key>",
"Content-Type: application/json"
],
]);
$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.datalegion.ai/person/search"
payload := strings.NewReader("{\n \"query\": \"SELECT * FROM people WHERE city = 'San Francisco' ORDER BY last_seen DESC\",\n \"limit\": 10,\n \"titlecase\": false\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("API-Key", "<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.datalegion.ai/person/search")
.header("API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"query\": \"SELECT * FROM people WHERE city = 'San Francisco' ORDER BY last_seen DESC\",\n \"limit\": 10,\n \"titlecase\": false\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.datalegion.ai/person/search")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"query\": \"SELECT * FROM people WHERE city = 'San Francisco' ORDER BY last_seen DESC\",\n \"limit\": 10,\n \"titlecase\": false\n}"
response = http.request(request)
puts response.read_body{
"matches": [
{
"person": {
"legion_id": "fdd85569-f0f0-53a9-bc60-089507193c28",
"full_name": "jane marie doe",
"first_name": "jane",
"last_name": "doe",
"city": "san francisco",
"state": "california",
"state_code": "US-CA",
"country": "united states",
"country_code": "US",
"job_title": "senior product manager",
"company_name": "tech company",
"company_domain": "techcompany.com",
"company_industry": "technology, information and internet",
"company_size": "1001-5000",
"seniority_level": "senior",
"job_function": "product",
"work_email": "jane.doe@techcompany.com",
"linkedin_url": "https://www.linkedin.com/in/janedoe",
"linkedin_id": "123456789",
"years_of_experience": 12,
"last_seen": "2026-01-20"
}
}
],
"total": 8492
}{
"error": "<string>",
"message": "<string>",
"details": {}
}{
"error": "<string>",
"message": "<string>",
"details": {}
}{
"error": "<string>",
"message": "<string>",
"details": {}
}{
"error": "<string>",
"message": "<string>",
"details": {}
}{
"error": "<string>",
"message": "<string>",
"details": {}
}{
"error": "<string>",
"message": "<string>",
"details": {}
}{
"error": "<string>",
"message": "<string>",
"details": {}
}{
"error": "<string>",
"message": "<string>",
"details": {}
}{
"error": "<string>",
"message": "<string>",
"details": {}
}{
"error": "<string>",
"message": "<string>",
"details": {}
}Person Search (Beta)
Person Search API endpoint: query profiles using SQL-like syntax with flexible filtering across 100+ data fields.
curl --request POST \
--url https://api.datalegion.ai/person/search \
--header 'API-Key: <api-key>' \
--header 'Content-Type: application/json' \
--data @- <<EOF
{
"query": "SELECT * FROM people WHERE city = 'San Francisco' ORDER BY last_seen DESC",
"limit": 10,
"titlecase": false
}
EOFimport requests
url = "https://api.datalegion.ai/person/search"
payload = {
"query": "SELECT * FROM people WHERE city = 'San Francisco' ORDER BY last_seen DESC",
"limit": 10,
"titlecase": False
}
headers = {
"API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
query: 'SELECT * FROM people WHERE city = \'San Francisco\' ORDER BY last_seen DESC',
limit: 10,
titlecase: false
})
};
fetch('https://api.datalegion.ai/person/search', 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.datalegion.ai/person/search",
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([
'query' => 'SELECT * FROM people WHERE city = \'San Francisco\' ORDER BY last_seen DESC',
'limit' => 10,
'titlecase' => false
]),
CURLOPT_HTTPHEADER => [
"API-Key: <api-key>",
"Content-Type: application/json"
],
]);
$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.datalegion.ai/person/search"
payload := strings.NewReader("{\n \"query\": \"SELECT * FROM people WHERE city = 'San Francisco' ORDER BY last_seen DESC\",\n \"limit\": 10,\n \"titlecase\": false\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("API-Key", "<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.datalegion.ai/person/search")
.header("API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"query\": \"SELECT * FROM people WHERE city = 'San Francisco' ORDER BY last_seen DESC\",\n \"limit\": 10,\n \"titlecase\": false\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.datalegion.ai/person/search")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"query\": \"SELECT * FROM people WHERE city = 'San Francisco' ORDER BY last_seen DESC\",\n \"limit\": 10,\n \"titlecase\": false\n}"
response = http.request(request)
puts response.read_body{
"matches": [
{
"person": {
"legion_id": "fdd85569-f0f0-53a9-bc60-089507193c28",
"full_name": "jane marie doe",
"first_name": "jane",
"last_name": "doe",
"city": "san francisco",
"state": "california",
"state_code": "US-CA",
"country": "united states",
"country_code": "US",
"job_title": "senior product manager",
"company_name": "tech company",
"company_domain": "techcompany.com",
"company_industry": "technology, information and internet",
"company_size": "1001-5000",
"seniority_level": "senior",
"job_function": "product",
"work_email": "jane.doe@techcompany.com",
"linkedin_url": "https://www.linkedin.com/in/janedoe",
"linkedin_id": "123456789",
"years_of_experience": 12,
"last_seen": "2026-01-20"
}
}
],
"total": 8492
}{
"error": "<string>",
"message": "<string>",
"details": {}
}{
"error": "<string>",
"message": "<string>",
"details": {}
}{
"error": "<string>",
"message": "<string>",
"details": {}
}{
"error": "<string>",
"message": "<string>",
"details": {}
}{
"error": "<string>",
"message": "<string>",
"details": {}
}{
"error": "<string>",
"message": "<string>",
"details": {}
}{
"error": "<string>",
"message": "<string>",
"details": {}
}{
"error": "<string>",
"message": "<string>",
"details": {}
}{
"error": "<string>",
"message": "<string>",
"details": {}
}{
"error": "<string>",
"message": "<string>",
"details": {}
}Query Format
Write aSELECT * FROM people WHERE ... query. Do not include LIMIT or OFFSET clauses in the SQL; use the limit and offset parameters instead.
{
"query": "SELECT * FROM people WHERE company_name ILIKE '%google%' AND state = 'california'",
"limit": 5
}
Response Shape
{
"matches": [ { "person": { "..." : "..." } } ],
"total": 8492
}
-
matches: capped at the request’slimit(max 100). -
total: the full row count matching your WHERE clause across the database, not just the returned page. Useful for TAM-style sizing: query for “VPs of Engineering at fintech in NY” withlimit: 1andtotaltells you how many candidates exist. The companionTotal-Count-Statusresponse header tells you howtotalwas derived:exact(the true count, for selective queries like the one above),estimate(on broad, low-selectivity queries the exact count exceeds its time budget, sototalbecomes the query planner’s row estimate; treat it as a magnitude, e.g. render “~140,000”, not a precise number), orpage(neither available;totalis just the returned page size). Search responses do not includematch_metadata(that field is only populated by/person/enrich, where there’s a single input identifier to match against). A broad query returns the same response shape with an approximate total, flagged by the header:Total-Count-Status: estimate{ "matches": [ { "person": { "..." : "..." } } ], "total": 140000 } -
Industry filters:
company_industryholds the LinkedIn industry names listed in Enums, lowercased. A filter no stored value can satisfy, such as an older LinkedIn name incompany_industry ILIKE '%information technology%', returns an empty page at once, free of charge, with aSearch-Warningresponse header naming the filter and the closest stored values.
Pagination
To page through results beyond the first 100, setoffset to the number of rows to skip. Maximum offset is 10,000.
{
"query": "SELECT * FROM people WHERE company_name ILIKE '%google%' AND state = 'california'",
"limit": 100,
"offset": 100
}
Available Columns
Text Columns
UseILIKE for partial matching or = for exact matching.
| Column | Description |
|---|---|
full_name | Full name |
first_name | First name |
last_name | Last name |
job_title | Current job title |
company_name | Current company name |
company_domain | Current company domain |
company_industry | Current company industry, a LinkedIn industry name (see Enums) |
city | Current city |
state | Current state (full name, e.g., california) |
state_code | State code (ISO 3166-2, e.g., US-CA) |
country | Current country (full name, e.g., united states) |
country_code | Country code (ISO 3166-1 alpha-2, e.g., US) |
Numeric Columns
| Column | Description |
|---|---|
age | Current age |
birth_year | Birth year |
years_of_experience | Total years of professional experience |
avg_tenure_months | Average tenure across jobs (months) |
linkedin_followers | LinkedIn follower count |
linkedin_connections | LinkedIn connection count |
Enum Columns
Use= or IN for exact matching.
| Column | Values |
|---|---|
sex | male, female |
seniority_level | c_level, owner, partner, vp, director, manager, senior, junior, training, intern |
job_function | engineering, information_technology, product, design, sales, marketing, operations, finance, hr, legal, customer_success, data, executive, social_services, healthcare, education, trades, transportation, service, other |
expense_category | general_and_administrative, research_and_development, sales_and_marketing, cost_of_services, not_applicable |
company_size | 1-10, 11-50, 51-200, 201-500, 501-1000, 1001-5000, 5001-10000, 10001+ |
highest_degree_level | doctorate, masters, bachelors, associates, high_school |
Boolean Columns
| Column | Values |
|---|---|
is_decision_maker | true, false |
is_platform_worker | true, false |
Cross-Table Columns
These columns search across related tables and are automatically rewritten to subqueries:| Column | Description |
|---|---|
skills | Skills from all positions |
languages | Spoken languages |
headline | LinkedIn headline text |
summary | LinkedIn summary / bio text |
title | All job titles from work history (not just current) |
organization_name | All employer names from work history |
description | Job description text from work history |
| Form | Example | Matches |
|---|---|---|
ILIKE with a leading % | organization_name ILIKE '%dell technologies%' | Rows containing the text |
ILIKE without a leading % | organization_name ILIKE 'cisco%' | Rows starting with the text |
= | organization_name = 'cisco' | Rows equal to the value, ignoring case |
- To exclude, wrap the filter:
NOT (organization_name ILIKE '%cisco%'). - To match several values, join filters with
OR. - Inside a value, double an apostrophe as standard SQL requires:
organization_name ILIKE '%mcdonald''s%'. - Other operators on these columns, such as
~*,NOT ILIKEorIN (...), return a 400 that lists the supported forms.
'%text%' matches the text anywhere, including inside a longer word: '%cisco%' also matches “San Francisco”. To match the text only where a word begins, pair a starts-with pattern with a pattern that has a space before the text:
organization_name ILIKE 'cisco%' OR organization_name ILIKE '% cisco%'
job_title, company_name or city) alongside a cross-table filter. A cross-table filter over a broad population, such as everyone in a metro, checks every person’s related rows and can exceed the time limit.
Example Queries
-- Engineers at Google in California
SELECT * FROM people WHERE company_name ILIKE '%google%' AND job_title ILIKE '%engineer%' AND state = 'california'
-- Senior leadership at large companies
SELECT * FROM people WHERE seniority_level IN ('vp', 'c_level', 'director') AND company_size = '10001+'
-- People with Python skills in New York
SELECT * FROM people WHERE skills ILIKE '%python%' AND state = 'new york'
-- Systems engineers in Sunnyvale who have worked at Cisco
SELECT * FROM people WHERE job_title ILIKE '%systems engineer%' AND city = 'sunnyvale'
AND (organization_name ILIKE 'cisco%' OR organization_name ILIKE '% cisco%')
-- Decision makers in marketing
SELECT * FROM people WHERE is_decision_maker = true AND job_function = 'marketing'
Authorizations
Body
Request model for SQL query-based person search.
SQL query to execute (must not contain LIMIT clause). Must be a SELECT query only.
1 - 10000Maximum number of results to return (replaces LIMIT in query)
1 <= x <= 100Number of results to skip for pagination (0-10000). Stable within a build; ordering may change across builds.
0 <= x <= 10000If true, format text fields in title case (names, job titles, company names, locations, skills, headlines). Raw fields, IDs, URLs, codes, and confidence fields are excluded.
Comma-separated list of fields to include in response. If omitted, all fields are returned.
Comma-separated list of fields to exclude from response. Applied after include_fields filter.
If true, pretty-print JSON response with indentation.
Response
Success - query executed and results returned
Multiple person matches response.
List of matches sorted by confidence (descending). Capped at the request's limit.
Show child attributes
Show child attributes
For /person/search and /person/discover, the total number of rows matching the query's WHERE clause across the database (not just this page). For /person/enrich, the number of matches found for the input identifier. On search/discover, if the exact count exceeds its time budget (broad, low-selectivity queries) it falls back to the query planner's row estimate, and if that is unavailable, to the size of the returned page; the response header Total-Count-Status reports which (exact, estimate, or page).