The Query API

The Query API is for when you know exactly the answer you want. You describe it: who to count, in which place, broken down by what, with which numbers for each group. Hiasynth computes it from the whole population, combining any attributes of people and of their homes in one query, and hands back one entry per group, ready for your dashboard, model or pipeline.

The tools answer a question in one call, written for an assistant to read. The Query API answers a specification, written for code to read. Reach for it when your question has a shape of its own: an audience defined by either-or conditions, an age pyramid crossed with income, every NUTS3 region of a country for a map, the share of each district's people living with an electric car, or the median household income of the children in each municipality.

Access comes with Builder

The Query API is part of Builder. Once Builder is on for your account, every API key on it can call the Query API. Create keys under API keys on your connector page. A key acts as your account, so keep it on your server.

What you can ask for

You want
You send
An audience with alternatives: under 25 or over 70
where with an any group
A breakdown by up to four things at once
group_by with attributes, bands or geography
Age in bands rather than single years
"attribute": "age", "bins": [18, 30, 45, 65]
Counts per municipality, NUTS2 or NUTS3 region, or statistical unit
"geo": "lau", "geo": "nuts3" …
Averages, medians, percentiles, sums
metrics
The share of each group meeting a condition
a rate metric
How concentrated the audience is in each group
"compare_to": "scope"
Every group, page by page
limit and offset

Your first query

People in Munich by sex, with the median age of each:

curl https://mcp.hiasynth.co/v1/query \
  -H "Authorization: Bearer $HIASYNTH_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "place": "Munich",
    "group_by": ["sex"],
    "metrics": [{ "op": "median", "attribute": "age" }]
  }'

The answer:

{
  "fields": ["sex", "count", "share_pct", "median_age"],
  "groups": [
    { "sex": "F", "count": 756864, "share_pct": 51, "median_age": 40 },
    { "sex": "M", "count": 727251, "share_pct": 49, "median_age": 38 }
  ],
  "total": { "count": 1484115, "median_age": 39 },
  "page": { "returned": 2, "available": 2, "suppressed": 0, "offset": 0, "next_offset": null },
  "scope": {
    "label": "München (city DE003C)", "level": "city", "population": 1484115,
    "other_matches": [{ "label": "München (metro area DE003F)", "level": "metro", "code": "DE003F", "population": 2977659 }]
  },
  "unit": "people",
  "min_cell": 100,
  "data_vintage": 2024
}

other_matches shows that Munich also names a metro area of nearly three million. Send "code": "DE003F" to ask about that instead.

The address

Base URL
https://mcp.hiasynth.co
Query
POST /v1/query
Several queries at once
POST /v1/query/batch, up to 10
Attribute catalogue
GET /v1/attributes, free
Data release
GET /v1/version
OpenAPI document
GET /v1/openapi.json, public
Auth
Authorization: Bearer YOUR_API_KEY, or an OAuth access token

The OpenAPI document describes every endpoint and schema. Load it into Postman, or generate a typed client from it with any OpenAPI generator.

Anatomy of a query

Every field is optional. An empty body counts everyone in Europe.

{
  "unit": "people",
  "place": "Stockholm",
  "where": [ ... ],
  "group_by": [ ... ],
  "metrics": [ ... ],
  "compare_to": "scope",
  "order_by": { "field": "lift", "desc": true },
  "limit": 100,
  "offset": 0
}
  • unit is what each count counts: people (the default) or households. Attributes of people and of their homes work together whichever you count.
  • The place is place, a name or code from a country down to a district, or near with a radius_km, or postcode. Answers come back in seconds when a place is named.
  • where is who to count: conditions on attributes, combined with and, with any and all groups for or-logic.
  • group_by is how to break the count down: categorical attributes, numeric bands and one geography level.
  • metrics are the numbers to compute for each group, on top of the count and share that always come back.
  • compare_to adds the population of each group, the audience's share of it, and the lift against the whole place.
  • order_by, limit and offset sort the groups and page through them.

The reference covers every field, operator and limit. The recipes are complete queries for common jobs.

Reading the answer

  • groups holds one entry per group, with its dimension values, count, share_pct (its share of everyone counted), and each metric under its name.
  • fields lists the names in each group, in order.
  • total is the same numbers for everyone counted, across all groups.
  • page says how many groups there are and where the next page starts. next_offset is null on the last page.
  • scope is the place the query ran in, its level and population. When a name could mean several places, other_matches lists the rest.
  • notes explain anything that shaped the numbers, such as a condition that leaves out children.
  • The Hiasynth-Data-Version response header names the population release that answered. Key your caches on it.

Privacy, built in

The population is synthetic, and it is fit to real statistics for real places. So the API protects the people behind places.

  • A group of fewer than 100 people is withheld from groups and counted in page.suppressed. When the withheld groups add up to fewer than 100 people, the next-smallest group is withheld with them, so the total never reveals a small group.
  • A metric computed from fewer than 100 values comes back as null, with a note.
  • Health, religion, sexual orientation and political attributes answer in a place as small as a NUTS2 region and broken down by country. With a smaller place or a finer geographic breakdown they come back as "suppressed": true.
  • When a whole query lands under the floor, the answer is "suppressed": true with a recovery that says which lever to pull: the place or the audience.

Credits

Call
Credits
A query that returns an answer
2
Each query in a batch that returns an answer
2
A query that returns an error or an unresolved place
0
The attribute catalogue, the version, the OpenAPI document
0

A suppressed answer counts as an answer: the query ran, and the floor is the finding. Credits come from your account's allowance and packs, the same ones every Hiasynth tool draws on; see pricing.

Errors

Errors come back as JSON with an HTTP status your code can branch on, and a message written to be shown or logged as it is.

Status
error
What to do
400
invalid_query, invalid_args, invalid_json
Change what the message names
401
missing_or_malformed_api_key, invalid_api_key
Send a valid key
402
no_subscription, insufficient_credits
Subscribe or top up
403
builder_access_required
Request Builder for the account
422
not_found and similar, with resolved: false
Use a code, or add country
503
engine_busy
Retry after a moment
504
query_timeout
Name a place, or narrow the query

Every query is read-only and idempotent, so retrying is always safe.