API reference
Search, then fetch
People, jobs, and companies are searched with the same Query Spec tree. Every search hands back encrypted IDs, and a detail route turns the IDs you keep into records.
Search, then fetch
This is the one thing to understand before writing any code. A search response contains encrypted string IDs and no record data at all. To read the records you send those IDs to the detail route for the same dataset, in batches of up to 100. There is no endpoint that does both.
# 1. Search. The response is IDs, a total, and a next cursor.
ids=$(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"}]}, "size": 25}' \
| jq '.data.company_ids')
# 2. Fetch. The response is records, for the IDs step 1 returned.
jq -n --argjson ids "$ids" '{company_ids: $ids}' |
curl -X POST "https://mira-api.metix.ai/entity/v1/companies/detail-by-id" \
-H "Authorization: Bearer $METIX_KEY" \
-H "Content-Type: application/json" \
-d @-
# {"code":200,"msg":"ok","data":{"total":25,"found":25,"not_found":[],"results":[ ... ]}}Why it is split
Searching a market and reading records are priced differently, and the split is what lets you use that. A search bills one API Credit per twenty-five ids, a record costs one per five, so finding is roughly five times cheaper per row than reading. Sweep wide, keep the ids that matter, and pay for records only where you actually wanted them.
One grammar, three vocabularies
All three search routes take the same where tree: composers all, any, and not around leaves that apply one operator to one field. What changes between datasets is the field list, not the grammar.
{
"where": {"all": [
{"field": "<name>", "<operator>": <value>}
]},
"size": 25
}A leaf carries exactly one operator, so a bounded range is an all of two leaves rather than gte and lte in one node. The field decides which operators it takes: free-text fields take match, fields compared whole (categories, names, codes, yes or no) take eq or in, and numbers and dates take the range operators. GET /contract lists them for every field, and the query spec page covers composition, operators, relative dates, same-record scopes, and cursor paging.
Sizes, totals and paging
A search takes an optional size and answers with the ids it found, a total, and a next cursor while more remain. The same three rules hold on all three datasets.
- Field
- size
- Rule
- Omit it and you get 100. The maximum is 10,000. Every id in the window is billable, so ask for the page you intend to read rather than the ceiling.
- Field
- total
- Rule
- How many matched, not how many came back. An exact integer below 100,000 and the string "100000+" at or above it, so read it as a union rather than as a number.
- Field
- next
- Rule
- Send it back as after, with the same where tree, for the following page. It is absent once there is nothing left to fetch.
/v1/jobs/query// Page 1 response
{"data": {"job_ids": ["Jb71xKcAoP"], "total": 4278, "next": "<opaque cursor>"}}
// Page 2 request: the same where, and next sent back unchanged as after
{"where": {"all": [{"field": "title", "match": "data engineer"}]},
"size": 100,
"after": "<the next value from page 1>"}total is the field to read before paging at all: it tells you whether the filter is narrow enough to be worth walking, and it costs nothing extra to look at.
Pages are not a snapshot
The cursor carries a position in the sort order and nothing else, so the server stores no state and a cursor never expires. It is sealed and only means something to the search that issued it: send it back unchanged, because an edited or hand-written cursor is refused with invalid_cursor. The cost of that is consistency between pages: a record indexed while you are paging can appear later, move, or be missed. If you need an exact set, narrow the query rather than paging deeper.
What a call costs
API Credits are spent per result, not per request, and the rate is the same on every plan. A larger plan buys a larger monthly allowance rather than a different rate.
- Call
- Query Spec search
- API Credits
- ceil(ids / 25)
- Charged when
- Results came back. A search that matched nothing is free
- Call
- Natural-language people search
- API Credits
- 5 + ceil(ids / 25)
- Charged when
- Any successful response, including one that matched nothing
- Call
- Detail
- API Credits
- ceil(records / 5)
- Charged when
- Records came back. Ids that resolved to nothing are free
Nothing is charged for a refused request, a timeout or a 5xx, so a malformed query costs only the round trip. credits carries the settlement rules and the plan allowances.
Authentication
Every route below the system endpoints takes a bearer key. Create one at https://platform.metix.ai/api-keys; keys begin with metix_, and one issued before that rename begins with mira_ and is still valid.
export METIX_KEY="metix_xxxxxxxxxxxx"
curl -s "https://mira-api.metix.ai/auth/key/status" \
-H "Authorization: Bearer $METIX_KEY"GET /auth/key/status reports whether the key is active, its scopes, its rate limit, and the quota it has left, which makes it the cheapest way to tell a bad key apart from an exhausted balance. A 401 carries missing_api_key or invalid_api_key and docs_url to the run. Other public error_code values are on the error-codes page.
The three datasets
Everything above is the same on all three. What differs is the field vocabulary, and each dataset carries its own: a page for searching it, and a page for the record that comes back. They share no field names, so a body written for one is refused by the other two.
The three are joined by identifiers rather than by names. A job carries the hiring company's id and a profile carries each employer's, and both resolve at the companies detail route, so you can walk from a search on one dataset into records on another without matching strings.
Contact
Contact unlock turns a person you found into a way to reach them: a personal email, a work email or a phone number, charged per value returned and never for one we could not find. Probe asks whether a value exists before you spend on it. See contact.
System routes
/version needs no key and answers two questions. version is the release this build calls itself. contract_hash fingerprints the published contract: it changes when a route, a parameter, a limit or a documented response shape changes, and not otherwise, which makes it the one to pin.
curl -s "https://mira-api.metix.ai/version"
# {"code":200,"msg":"ok","data":{
# "version":"2.1.2","contract_hash":"08f0406173520512"}}Pin contract_hash when you integrate. A request that these pages say should work and does not is worth checking against it before anything else: the same value means the contract has not moved and the problem is in the query, and a different one means it has, and the changelog says how.
GET /contract returns that contract as JSON: every route with its parameters and limits, and the queryable field vocabulary for each dataset. It takes your key and costs nothing. These pages are written; that is generated from the API answering you, so it describes what is being served rather than what was true when a page was last edited.
curl -s "https://mira-api.metix.ai/contract" \
-H "Authorization: Bearer $METIX_KEY" | jq '.data.endpoints[].path'- Method
- GET
- Path
- /version
- Purpose
- Read the deployed version and contract fingerprint
- Method
- GET
- Path
- /contract
- Purpose
- Read the live contract: routes, query vocabulary, the operators each field takes, limits, billing
- Method
- GET
- Path
- /docs
- Purpose
- Teaching catalog for this API: pages, examples, field notes, error recovery
- Method
- GET
- Path
- /auth/key/status
- Purpose
- Inspect authenticated key status, scopes, rate limit, and quota projection