API reference

People

Search profiles with a Query Spec tree, or ask in plain language. Both return encrypted profile IDs, and the detail route turns those IDs into records.

Routes
3
Search
/v1/people/query
Search returns
encrypted IDs
Detail batch
up to 100 IDs
  • POST/v1/people/queryQuery Spec search
  • POST/v1/people-searchNatural-language search
  • POST/entity/v1/profiles/detail-by-idFetch profile records

Search, then fetch

Search never returns profile records. It returns encrypted string IDs, and you pass up to 100 of them to the detail route to read the records. There is no one-shot endpoint, so plan for two calls.

# 1. Search. Returns encrypted IDs, never records.
curl -X POST "https://mira-api.metix.ai/v1/people/query" \
  -H "Authorization: Bearer $METIX_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "where": {"all": [
      {"field": "city", "eq": "San Francisco"},
      {"has_experience": {"all": [
        {"field": "company_name", "match": "Google"},
        {"field": "level", "eq": "Director"},
        {"field": "is_current", "eq": true}
      ]}}
    ]},
    "size": 25
  }'

# 2. Read the records, up to 100 IDs per call.
curl -X POST "https://mira-api.metix.ai/entity/v1/profiles/detail-by-id" \
  -H "Authorization: Bearer $METIX_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "profile_ids": ["kR3nQv8xTm", "9tQmLd2wYb"],
    "_source": ["profile_id", "full_name", "active_experience_title", "skills"]
  }'

Two calls, always

A search response carries IDs and nothing else. If your code expects records back from a search, it will read an empty payload and conclude there is no data. Send the IDs to the detail route, in batches of up to 100.

When to use it

  • Build a profile set by role, skills, employer, education, location, or seniority.
  • Express what a flat filter list cannot: alternatives, negation, and numeric or date ranges.
  • Ask for a person who held one specific job, rather than a person who separately matched two facts.
  • Ask in natural language when the constraints are awkward to write out as a tree.

Request parameters and limits

Parameter
where
Type
object
Notes
Query Spec tree. Required by /v1/people/query
Parameter
size
Type
integer (1-10000)
Notes
Profile IDs in one page. Omitting it returns 100
Parameter
after
Type
string
Notes
Cursor. Pass the next value from the previous response
Parameter
text
Type
string (1-5000 chars)
Notes
Natural-language question for /v1/people-search. That route accepts text and size only
Parameter
profile_ids
Type
string[] (1-100)
Notes
IDs from a search, sent to the detail route
Parameter
_source / source
Type
array, object, or boolean
Notes
Field selection on detail. Omit it for the default record; name fields to trade it for a narrower or a wider one
  • One operator per leaf. A bounded range is an all of two leaves, not gte and lte in one node. This is the first mistake almost every caller makes.
  • A field name that is not in this dataset's list is refused, so a typo is visible rather than returning a record without it. The three datasets do not share one vocabulary.
  • Search returns at most 10,000 IDs per page and 100 when size is omitted; detail accepts at most 100 IDs per request.
  • Scope nodes exist for people only. Jobs and companies are one document each, so there is nothing to group.
  • Every Query Spec search reports total: an exact integer below 100000, and the string "100000+" at or above it. It counts what matched, not what was returned. Read it as a union, not as an integer.
  • Profile data is a periodically refreshed snapshot and can lag real-world changes.
  • The default record is a subset. Naming fields in _source reaches the full published set: the subject's own links and prose, their employer as a company, and their institution.
  • Prose fields carry no email addresses or phone numbers. Contact data is a separate surface with its own pricing.

Credit cost

Query Spec search costs ceil(returned IDs / 25) Credits, and 0 when it returns nothing. Natural-language search costs 5 + ceil(returned IDs / 25) Credits, and keeps the 5 Credit base on any successful response. Profile detail costs ceil(found records / 5) Credits, and not_found rows are free.

Queryable fields

These are the only names a leaf may put in field. The grammar around them, including composition, one operator per leaf, and relative dates, is on the Query Spec page. What a response may contain is a separate, wider list: a field can be returnable without being queryable.

active_titleactive_departmenttitleheadline

Role text. active_title and active_department describe the current job; title matches any job held.

Operators: match, eq, in, exists

{
  "where": {
    "all": [
      {
        "field": "active_title",
        "match": "machine learning engineer"
      },
      {
        "field": "country",
        "eq": "United States"
      }
    ]
  }
}
company_nameinstitution_name

Employer and school names.

Operators: match, eq, in, exists

{
  "where": {
    "all": [
      {
        "has_experience": {
          "all": [
            {
              "field": "company_name",
              "match": "Google"
            },
            {
              "field": "is_current",
              "eq": true
            }
          ]
        }
      }
    ]
  }
}
skillslanguagescertificationscoursesawardspublicationspatentsmajor

List and free-text attributes attached to the person.

Operators: match, eq, in, exists

{
  "where": {
    "all": [
      {
        "field": "skills",
        "match": "kubernetes"
      },
      {
        "has_language": {
          "all": [
            {
              "field": "languages",
              "eq": "Chinese"
            }
          ]
        }
      }
    ]
  }
}
language_proficiency

How well one language is spoken, as one of five levels. Put it in the same has_language as the language it describes, or it will be satisfied by any language on the profile.

Operators: eq, in, exists

{
  "where": {
    "all": [
      {
        "has_language": {
          "all": [
            {
              "field": "languages",
              "eq": "Chinese"
            },
            {
              "field": "language_proficiency",
              "eq": "Professional working proficiency"
            }
          ]
        }
      },
      {
        "field": "country",
        "eq": "United States"
      }
    ]
  }
}
full_namefirst_namelast_namelinkedin_url

Identity, matched exactly.

Operators: eq, in, exists

{
  "where": {
    "all": [
      {
        "field": "full_name",
        "eq": "Maria Garcia"
      }
    ]
  }
}
citystatecountrycountry_iso2country_iso3regions

Where the person is. country takes a name such as United States.

Operators: eq, in, exists

{
  "where": {
    "all": [
      {
        "field": "city",
        "eq": "San Francisco"
      },
      {
        "field": "country",
        "eq": "United States"
      }
    ]
  }
}
workplace_cityworkplace_stateworkplace_country

Where a job is. Most useful inside has_experience, where it binds to one job.

Operators: eq, in, exists

{
  "where": {
    "all": [
      {
        "has_experience": {
          "all": [
            {
              "field": "workplace_city",
              "eq": "Berlin"
            },
            {
              "field": "is_current",
              "eq": true
            }
          ]
        }
      }
    ]
  }
}
rolelevelmanagement_levelindustrycompany_type

Platform vocabulary, matched exactly rather than fuzzily.

Operators: eq, in, exists

{
  "where": {
    "all": [
      {
        "has_experience": {
          "all": [
            {
              "field": "role",
              "eq": "Engineering and Technical"
            },
            {
              "field": "level",
              "eq": "Director"
            },
            {
              "field": "is_current",
              "eq": true
            }
          ]
        }
      }
    ]
  }
}
is_workingis_decision_makeris_currentis_studying

Booleans. is_current and is_studying describe a record, so they belong in a scope.

Operators: eq, in, exists

{
  "where": {
    "all": [
      {
        "field": "is_working",
        "eq": true
      },
      {
        "field": "is_decision_maker",
        "eq": true
      }
    ]
  }
}
experience_monthsduration_months

Time spans in months. experience_months is career total; duration_months is one job.

Operators: gte, lte, exists

{
  "where": {
    "all": [
      {
        "field": "experience_months",
        "gte": 60
      },
      {
        "field": "experience_months",
        "lte": 180
      }
    ]
  }
}
company_employees_countcompany_size_range

Employer size. company_size_range is an ordinal band, so it compares as a number.

Operators: gte, lte, exists

{
  "where": {
    "all": [
      {
        "has_experience": {
          "all": [
            {
              "field": "company_employees_count",
              "gte": 1000
            },
            {
              "field": "is_current",
              "eq": true
            }
          ]
        }
      }
    ]
  }
}
degree_levelinstitution_rankinggraduation_yearstudy_start_year

Education, as ordinals and years.

Operators: gte, lte, exists

{
  "where": {
    "all": [
      {
        "has_education": {
          "all": [
            {
              "field": "degree_level",
              "gte": 2
            },
            {
              "field": "major",
              "match": "computer science"
            }
          ]
        }
      }
    ]
  }
}
started_atended_at

Job dates. Takes YYYY-MM-DD or a relative offset such as now-6m.

Operators: gte, lte, exists

{
  "where": {
    "all": [
      {
        "has_experience": {
          "all": [
            {
              "field": "ended_at",
              "gte": "now-1y"
            }
          ]
        }
      }
    ]
  }
}

Same-record scopes

People hold many jobs and many degrees, so people are the only entity with same-record scopes: has_experience, has_education, and has_language. Every condition inside one scope must match the same record. That is the difference between a director at Google and someone who worked at Google and was a director somewhere else.

Fields with a fixed set of values

These are matched against the whole stored value, so the spelling and the capitalisation both have to match. Getting either wrong is the one mistake in the grammar that raises no error: the query is valid, it costs nothing, and it returns zero, which reads as an answer about the data rather than about the query.

Field
language_proficiency
Values
Elementary proficiencyLimited working proficiencyProfessional working proficiencyFull professional proficiencyNative or bilingual proficiencyThe whole scale, lowest to highest. A record carries the same five strings, so a level you read is a level you can filter on. Nothing else is accepted, including numbers and words like fluent or advanced, because those mean different levels to different people.
Field
management_level, level
Values
SpecialistSeniorManagerHeadDirectorVice PresidentPresident/Vice PresidentC-LevelPartnerFounderOwnerInternlevel is the same set inside has_experience, where it describes one job rather than the person.
Field
active_department
Values
AdministrativeC-SuiteConsultingCustomer ServiceDesignEducationEngineering and TechnicalFinance & AccountingGeneral ManagementHuman ResourcesLegalMarketingMedicalOperationsOtherProductProject ManagementReal EstateResearchSalesTrades
Field
industry
Values
Accommodation ServicesAdministrative and Support ServicesConstructionConsumer ServicesEducationEntertainment ProvidersFarming, Ranching, ForestryFinancial ServicesGovernment AdministrationHolding CompaniesHospitals and Health CareManufacturingOil, Gas, and MiningProfessional ServicesReal Estate and Equipment Rental ServicesRetailTechnology, Information and MediaTransportation, Logistics, Supply Chain and StorageUtilitiesWholesaleThis is the twenty-value set used on a person's experience. A company's own industry field is a different, much longer vocabulary.

Not every category field is a fixed set

role, company_type and the free-text fields are not fixed sets. Ask them with match and expect a long tail.

Response shape

Successful responses use the public envelope with code, msg, and data.

Search returns IDs, and a next cursor while more pages remain:

{
  "code": 200,
  "msg": "ok",
  "data": {
    "profile_ids": ["kR3nQv8xTm", "9tQmLd2wYb"],
    "total": 418,
    "next": "WzE3MjQwMDAwMDAsImtSM25Rdjh4VG0iXQ"
  }
}

Detail returns the records, and names the IDs it could not resolve:

{
  "code": 200,
  "msg": "ok",
  "data": {
    "total": 2,
    "found": 1,
    "not_found": ["9tQmLd2wYb"],
    "results": [{
      "profile_id": "kR3nQv8xTm",
      "full_name": "Example Profile",
      "active_experience_title": "Staff Machine Learning Engineer",
      "skills": ["Python", "distributed training"]
    }]
  }
}

The record itself is a page of its own: every field it can carry, what each one holds, and a whole one to read.

Access surfaces

Surface
REST
How to use this capability
POST /v1/people/query; POST /v1/people-search; POST /entity/v1/profiles/detail-by-id
Surface
Metix AI skill
How to use this capability
metix-people-search
Surface
MCP
How to use this capability
Use the MCP setup page to expose the same REST capability. Capability-specific tool names remain pending schema verification.
Surface
Natural-language prompt
How to use this capability
Find people who are currently directors at Google and based in San Francisco, then fetch their full records and summarize their skills.

Common next step: search the jobs dataset.