entity-people-search
Direct lookup of the canonical LinkedIn people entity via ask_onfire.
The people table ONFIRE.PEOPLE is the contact entity in the semantic
model. ask_onfire does not take SQL — it takes a structured
QueryIR (query={...}) that the server compiles for you. No external
API hop.
When to use this
- "Find security engineers at Meridian Bank"
- "Who are the CISOs in financial services in Germany?"
- "Show me people whose job summary mentions Sentinex"
- "Look up this LinkedIn URL: linkedin.com/in/johndoe"
- "Get me VPs of Engineering at companies with 200-500 employees"
- Any people lookup that can be expressed as filters on LinkedIn profile data
Skip this for:
- Prospecting with Forschung's persona/event/technology scoring → use
ai_prospecting - Looking up a single person by name only (no company context) → use
match_personfirst - People who joined specific communities → use the
community-join-signalsskill
ask_onfire shape
ask_onfire(query={
entity: "contact",
select: ["<field>", ...], // attributes / dimensions on the entity
filters: [{dimension, op, value}], // op: eq | in | gte | lte | contains
insight_filters: [{kind, value}], // persona / technology concepts (resolved server-side)
joins: [{entity, filters, insight_filters}], // filter-only single hop; cannot SELECT joined columns
distinct_by: "<field>",
order_by: [{field, direction}],
limit: <int> // ROW BUDGET — see Billing
})
Fields
Selectable attributes (returnable, NOT filterable)
| Field | Notes |
|---|---|
full_name |
First + last combined |
headline |
LinkedIn headline |
summary |
Career bio / About section |
job_summary |
Current role description (richest keyword field — but see caveat below) |
current_company_name |
Employer name |
location_name |
City, State |
job_start_date |
ISO date of current role start |
seniority_levels |
Seniority array (JOB_TITLE_LEVELS) — returnable only; filter seniority via persona insight_filters |
Filterable dimensions (use in filters)
| Dimension | Notes |
|---|---|
linkedin_url |
This person's profile. eq to look up ONE person. Any URL format is normalized server-side. |
job_title |
Free-text current title. Use op: "contains" for a keyword. |
job_title_role |
Normalised role category (e.g. "engineering") |
job_title_sub_role |
Finer role category |
location_country |
Stored lowercased — match lowercase literals (e.g. "united states"). Add it to select when these rows are headed for contact_data_enrichment — geo-routed tenants pick a region-specific waterfall from it. |
location_region |
State / region (lowercased) |
location_locality |
City (lowercased) |
industry |
Person-level industry (lowercased) |
current_company_url |
Employer LinkedIn URL. eq to target ONE company's people. Any URL format is normalized. |
current_company_industry |
Employer industry (lowercased) |
current_company_size |
e.g. "201-500" |
DELETED_AT is handled for you — the contact entity is filtered to live
profiles server-side. You no longer write a delete filter.
Persona / seniority / technology → insight_filters (NOT dimension filters)
Role, department, seniority, and technology/vendor are not columns —
they live in the insight catalog and go in insight_filters as concepts
(one concept per filter; multiple insight_filters are AND-ed):
{kind: "persona", value: "<role/department>"}— e.g."appsec","CISO","data engineering".{kind: "persona", value: "seniority_executive"}— seniority is a persona, not a dimension. Useseniority_executive(covers VP / C-suite) /seniority_director/seniority_teamlead/seniority_ic.{kind: "technology", value: "<tool/vendor>"}— e.g."Sentinex". This is the correct replacement for the old "keyword in job summary" lookup when the keyword is a technology or vendor.
Values are resolved server-side to an exact insight_name; if unsure,
confirm the canonical value with resolve_insights (carrying kind)
first. The server bounces back suggestions if it can't resolve.
Billing — read before calling
ask_onfire bills the client 1 credit per row returned.
- Always set a small explicit
limit(the row budget). Set it to exactly the number the user asked for; for a quick look 5–10 rows is plenty. - If you do not know how many rows the user wants, leave
limitunset:ask_onfirethen returns how many rows match without billing so you can confirm the count and resubmit. - If the response is
needs_confirmation(the row-budget gate — triggered whenlimitis unset or larger than ~50 rows and not yet confirmed), nothing was billed and no rows came back. Lowerlimitto what you actually need and resubmit. Do not reflexively setconfirmed: truejust to push a large pull through.
QueryIR templates
By company LinkedIn URL
ask_onfire(query={
entity: "contact",
select: ["linkedin_url", "full_name", "job_title", "seniority_levels",
"job_summary", "location_name", "job_start_date"],
filters: [{dimension: "current_company_url", op: "eq",
value: "linkedin.com/company/meridian-bank"}],
order_by: [{field: "job_start_date", direction: "desc"}],
limit: 50
})
By job title keyword
ask_onfire(query={
entity: "contact",
select: ["linkedin_url", "full_name", "job_title", "seniority_levels",
"current_company_name", "current_company_url"],
filters: [
{dimension: "job_title", op: "contains", value: "security engineer"},
{dimension: "location_country", op: "eq", value: "united states"}
],
limit: 50
})
By technology footprint (replaces "keyword in job summary")
A tool/vendor keyword (e.g. Sentinex) is a technology insight, not a
profile substring — pass it as an insight_filter, not a job_summary
match (job_summary is selectable but not filterable):
ask_onfire(query={
entity: "contact",
select: ["linkedin_url", "full_name", "job_title",
"current_company_url", "current_company_name", "job_summary"],
filters: [{dimension: "current_company_url", op: "eq",
value: "linkedin.com/company/northwind"}],
insight_filters: [{kind: "technology", value: "Sentinex"}],
limit: 30
})
insight_filters AND together — to cover several technologies, run one
query per technology and merge (there is no OR).
By seniority (a persona, not a column)
Seniority is not a dimension — filter it with a persona insight_filter
(VP / C-suite map to seniority_executive):
ask_onfire(query={
entity: "contact",
select: ["linkedin_url", "full_name", "job_title", "seniority_levels",
"current_company_name", "current_company_industry", "location_country"],
filters: [{dimension: "current_company_industry", op: "contains", value: "financial"}],
insight_filters: [{kind: "persona", value: "seniority_executive"}],
limit: 50
})
By LinkedIn URL (single person lookup)
ask_onfire(query={
entity: "contact",
select: ["linkedin_url", "full_name", "job_title", "seniority_levels",
"current_company_name", "current_company_url", "location_name",
"location_country", "summary"],
filters: [{dimension: "linkedin_url", op: "eq",
value: "https://www.linkedin.com/in/johndoe"}],
limit: 1
})
Output handling
ask_onfire returns a dataset handle + preview_rows (first 20).
- Show
preview_rowsto the user as a formatted table. - State
total_count— if it's more than 20, tell the user the full set is in the dataset. - Offer
download_dataset(dataset_id="...")for the full CSV. - For follow-up filters ("show me only the VPs", "add their company size"),
use
query_datasetsagainst thedataset_id— do not re-runask_onfire(re-running rebills per row).
What this entity CANNOT express
ask_onfire is a structured query, not SQL. The following are not
expressible against contact — do not try to fake them:
- Filtering on
skills—skillsis an array dimension, not scalar-filterable. If the user wants people by a skill tag, that filter cannot be applied here; the closest expressible proxy is atechnologyinsight_filter when the skill is actually a tool/vendor. - Arbitrary free-text matches on
job_summary/summary/headline— these are selectable attributes, not filterable. If the keyword is a technology/vendor → useinsight_filters kind=technology(above). If it is a role/department → useinsight_filters kind=persona. If it is genuinely arbitrary free text with no insight equivalent, it cannot be filtered here — say so rather than fabricating a filter. - GROUP BY distributions, aggregations beyond
contact_count, window functions, or temporal math — not supported.
Common pitfalls
- Putting persona / role / seniority / technology in
filters— they are not columns. They go ininsight_filters(seniority askind: "persona", valueseniority_executiveetc.). - Trying to filter
job_summary/summary/headline— selectable only. For a tool/vendor keyword use atechnologyinsight_filter instead. - Leaving
limitunset on purpose — that triggers the row-budget gate and returns no rows. Set the budget to what the user actually wants. - Re-running
ask_onfirefor "show me only the engineers" — slice the existing dataset withquery_datasetsto avoid rebilling.