Get started
Search wide, then read narrow.
Every search answers with encrypted ids, and a second call turns the ids you keep into records. Searching is the cheap half, so filter as hard as you like before you read. All three datasets work exactly this way.
Four ways to ask
Same request, four interfaces. The top two are sentences you type at an agent; the bottom two are calls you build. Switch between them and the bottom half stays the same, because it is the same two endpoints underneath.
Nothing to look up. The skill carries the field vocabulary and the two-step shape, so plain English is the whole interface.
Find machine learning engineers in San Francisco,
then show me their current titles and employers.# The skill already knows both calls and the field names.
POST /v1/people/query
{"where": {"all": [
{"field": "active_title", "match": "machine learning engineer"},
{"field": "city", "match": "San Francisco"}]}, "size": 25}
POST /entity/v1/profiles/detail-by-id
{"profile_ids": [ ...the 25 ids from above ]}No key yet? Create one in the dashboard. The free plan starts with 100 Credits and needs no card.
The shape of every request
Every search route answers with encrypted string ids and no record data at all. To read the records you send up to 100 of those ids to the detail route for the same dataset. There is no single call that does both, and no phrasing of a search that makes records come back early.
The practical consequence is worth stating on its own: a response full of ids means the search worked. Code that reads a search response looking for names finds nothing and concludes the dataset is empty. It is not. The names are one call away.
- Dataset
- People
- Search with
- POST /v1/people-search or /v1/people/query
- Then read with
- POST /entity/v1/profiles/detail-by-id
- Ids are called
- profile_ids
- Dataset
- Jobs
- Search with
- POST /v1/jobs/query
- Then read with
- POST /entity/v1/jobs/detail-by-id
- Ids are called
- job_ids
- Dataset
- Companies
- Search with
- POST /v1/companies/query
- Then read with
- POST /entity/v1/companies/detail-by-id
- Ids are called
- company_ids
Ids are permanently stable. Store one today and the same detail call resolves it months from now, so a pipeline can keep ids and re-read records on its own schedule rather than re-running the search.
Wire it into an agent
We run an MCP server at https://mira-api.metix.ai/mcp over streamable HTTP, with legacy SSE on /sse for older clients. Both authenticate with the same Bearer key as the REST API and settle against the same Credit balance. One line registers it. Keep --scope user: without it the server is registered against the directory you happened to run the command in, and it will be missing from the next project you open.
claude mcp add --scope user --transport http metix \
https://mira-api.metix.ai/mcp \
--header "Authorization: Bearer $METIX_KEY"Codex takes the key from your environment instead of writing it into the config file:
codex mcp add metix \
--url https://mira-api.metix.ai/mcp \
--bearer-token-env-var METIX_KEYAny other MCP client needs the same three facts: the URL above, the transport, and an Authorization: Bearer header carrying your key.
Agents that do not speak MCP can install the skills instead, which are plain instructions describing these endpoints, the field vocabulary, and the two-step shape. They read the same METIX_KEY as everything else on this page, and they install for Codex as well as for Claude Code:
npx skills add MetixAI-Official/metix-skills
export METIX_KEY="metix_xxxxxxxxxxxx"If the agent still has the retired OpenJobs skills installed, remove them first, or it will sometimes follow those instead. The skills page has the commands and a prompt you can hand the agent.
Reading this page as an agent
Run it yourself
Create a key under API Keys and export it once. Keys begin with metix_; one issued before that rename begins with mira_ and keeps working, so there is nothing to rotate. Keep the value out of source control and out of anything the browser can read.
export METIX_KEY="metix_xxxxxxxxxxxx"
# Free, and worth doing first: scopes, rate limit, remaining quota.
curl -s "https://mira-api.metix.ai/auth/key/status" \
-H "Authorization: Bearer $METIX_KEY"A 401 means the key is wrong or missing, a 403 means it is disabled or out of scope, and a 402 on a later call means the balance is exhausted.
Now the first call. Natural-language people search is the shortest route into the data: describe who you want and it returns ids.
/v1/people-searchcurl -s -X POST "https://mira-api.metix.ai/v1/people-search" \
-H "Authorization: Bearer $METIX_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "machine learning engineers in San Francisco", "size": 25}'{
"code": 200,
"msg": "ok",
"data": {
"profile_ids": [
"UHyKQXeFCLaGeBwgjwisBg",
"YhlkBSsJWv7zeGUJMaymMw",
"E-i6XGwZbYe-8QkHWd5Tbw"
]
}
}Ids, exactly as promised, and nothing else. Hand them to the profile detail route, up to 100 per request, and choose the fields you want back with _source. Omit _source and you get the default set.
/entity/v1/profiles/detail-by-idcurl -s -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": ["UHyKQXeFCLaGeBwgjwisBg"],
"_source": ["profile_id", "full_name", "active_experience_title", "skills"]
}'{
"code": 200,
"msg": "ok",
"data": {
"total": 1,
"found": 1,
"not_found": [],
"results": [
{
"profile_id": "UHyKQXeFCLaGeBwgjwisBg",
"full_name": "Jordan Reyes",
"active_experience_title": "Machine Learning Engineer",
"skills": ["machine learning", "model deployment", "pytorch"]
}
]
}
}Detail answers with found and not_found alongside the records, so a partial resolve is visible rather than silent. Ids that resolve to nothing are not charged.
Exact conditions, and the other two datasets
Natural language is for a brief. When the conditions are exact, a named employer, a country, a numeric range, use the Query Spec routes. All three datasets take the same tree, so the jobs and companies examples below are the people example with different field names.
A tree is composed with all, any and not, and each leaf carries exactly one of eq, in, match, gte, lte or exists.
# People: currently working machine learning engineers in San Francisco
curl -s -X POST "https://mira-api.metix.ai/v1/people/query" \
-H "Authorization: Bearer $METIX_KEY" \
-H "Content-Type: application/json" \
-d '{
"where": {"all": [
{"field": "active_title", "match": "machine learning engineer"},
{"field": "city", "eq": "San Francisco"},
{"field": "is_working", "eq": true}
]},
"size": 25
}'
# Jobs: US data engineering roles posted in the last 30 days
curl -s -X POST "https://mira-api.metix.ai/v1/jobs/query" \
-H "Authorization: Bearer $METIX_KEY" \
-H "Content-Type: application/json" \
-d '{
"where": {"all": [
{"field": "title", "match": "data engineer"},
{"field": "country_iso_2", "eq": "US"},
{"field": "posted", "gte": "now-30d"}
]},
"size": 25
}'
# Companies: US biotechnology companies with 50 to 500 employees
curl -s -X POST "https://mira-api.metix.ai/v1/companies/query" \
-H "Authorization: Bearer $METIX_KEY" \
-H "Content-Type: application/json" \
-d '{
"where": {"all": [
{"field": "industry", "match": "biotechnology"},
{"field": "hq_country", "match": "United States"},
{"field": "employees_count", "gte": 50},
{"field": "employees_count", "lte": 500}
]},
"size": 25
}'Each of those answers with the id list for its dataset, a total, and a next cursor while pages remain. Feed the ids to that dataset's detail route exactly as the people example does.
Two leaves, not two operators
{"field": "employees_count", "gte": 50, "lte": 500} is refused rather than half-applied.People carry same-record scopes
has_experience, has_education and has_language exist to say "one job that is both at Google and at director level" rather than "some job at Google, and separately some job at director level". Everything inside one scope must match the same record. See the Query Spec grammar.More than one page
A Query Spec response carries a next value while more results remain. Send it back as after to get the following page, and keep the rest of the request identical. When next is absent you have reached the last page.
# Page one answered with:
# "next": "WzE3MjQwMDAwMDAsImtSM25Rdjh4VG0iXQ"
curl -s -X POST "https://mira-api.metix.ai/v1/jobs/query" \
-H "Authorization: Bearer $METIX_KEY" \
-H "Content-Type: application/json" \
-d '{
"where": {"all": [
{"field": "title", "match": "data engineer"},
{"field": "country_iso_2", "eq": "US"},
{"field": "posted", "gte": "now-30d"}
]},
"size": 25,
"after": "WzE3MjQwMDAwMDAsImtSM25Rdjh4VG0iXQ"
}'Pages are not a snapshot
Where to go next
Credit settlement