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.

You type
Find machine learning engineers in San Francisco,
then show me their current titles and employers.
What the agent runs
# 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_KEY

Any 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

/llms.txt carries the same surface in one plain-text file: every route, what it returns, and the field vocabulary, without the HTML around it. Fetch that instead of scraping these pages.

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.

POST/v1/people-search
curl -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}'
200 OK
{
  "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.

POST/entity/v1/profiles/detail-by-id
curl -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"]
  }'
200 OK
{
  "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

The company example bounds headcount with two separate leaves. A leaf carries exactly one operator, so {"field": "employees_count", "gte": 50, "lte": 500} is refused rather than half-applied.

People carry same-record scopes

A profile holds many jobs and many degrees, so 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

The index keeps moving while you page through it. Two pages of one walk are not guaranteed to be consistent with each other, so a record can appear twice or not at all across a long pagination. Deduplicate on the id if that matters to you, and do not treat a full walk as a point-in-time export.

Where to go next

Credit settlement

A Query Spec search costs one Credit per 25 ids returned and nothing when it returns nothing. Natural-language people search adds a base of 5 Credits, which is charged even on an empty result. Detail costs one Credit per five records found, so five ids in one request cost 1 while five single-id requests cost 5. Read the Credits page before sizing a batch job.