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 — 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 }
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 |
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 |
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'
-- 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).
Was this page helpful?