For agents
Constraints resolve against fixed JSON paths with plain comparisons. There is no model at query time, and the three rules below are the whole contract.
Last updated 2026-10-01Version and scope
The registry publishes Itha Card 0.2. GET /api/vocab reports it as itha, and the two vocabulary files as vocab_versions (currently core 0.3, robotics 0.5). Cards written against 0.1 are still served and still match; each card states its own version in itha.
The index is industrial B2B: robots and automation, machine tools, farm, forestry and construction machinery, medical and laboratory equipment, uncrewed vehicles, marine, components, distributors, software and services. Most profiles are unclaimed, generated from public sources with a quote behind each fact. A few are verified by the seller. The record says which.
Query
POST https://itha.ai/api/search
content-type: application/json
{
"constraints": [
{"field": "category", "op": "eq", "value": "cobot"},
{"field": "payload_kg", "op": "at_least", "value": 10},
{"field": "ip_rating", "op": "ip_at_least", "value": "IP65"},
{ "op": "class_any", "value": ["machine_safety", "product_safety"]},
{"field": "price_usd", "op": "at_most", "value": 60000},
{"field": "lead_time_weeks", "op": "at_most", "value": 8},
{ "op": "serves", "value": "CA-ON"}
],
"status": "any",
"include_cards": false,
"limit": 25
}The same shape outside robotics. This one asks for machine tools from their manufacturer, with a certified management system, available in Germany. It uses core fields only, so it reads every card in the index:
POST https://itha.ai/api/search
content-type: application/json
{
"constraints": [
{"field": "category", "op": "eq", "value": "machine_tool"},
{"field": "role", "op": "eq", "value": "manufacturer"},
{ "op": "class_any", "value": ["quality_management"]},
{ "op": "serves", "value": "DE"}
]
}Ops: eq, at_least, at_most, ip_at_least, has_any, has_all, class_any, carries, serves. Every constraint accepts include_unknown; class_any also accepts require_status.
For a quick look, GET /api/search?q= takes one line of buyer language and parses it into the same constraints. The structured form is the contract.
GET /api/vocab returns the query surface as a document: every field with its ops, unit and include_unknown default, the category tree, the role, protocol, application and brand slugs, certifications with their class, the values observed in the live index with counts, and the region grammar. Fetch it before constraining on a slug you are unsure of. Vocabularies are open, so an unrecognised slug is not an error. It matches nothing, which looks exactly like an empty market.
The three rules
Categories are a tree
category is a core field on every offering, and its values form a tree: 353 categories under 36 roots, each entry in /api/vocab naming its parent. machine_tool is a root, cnc_machine sits under it, and lathe under that.
Roles
role is what the company is for one offering, not for the company: many manufacture some lines and distribute others. It takes eq, and there are 7: manufacturer, oem, distributor, reseller, integrator, service_provider, software_vendor. Aliases resolve here too.
To ask who resells a given brand, use carries with the brand slug or the brand’s domain. With serves beside it, availability resolves against that brand relationship’s own territory rather than the company’s.
Certifications
The vocabulary holds 443 certifications in 10 classes: machine_safety, product_safety, conformity_marking, quality_management, partner_program, operating_authorisation, procurement_eligibility, product_security, product_qualification, sustainability. class_any takes an array of classes and scans both the offering’s certifications and the company’s, and each match says which it was in scope. With no value it means machine_safety, product_safety, which is what “safety-certified” asks. The CE mark is conformity_marking, not a safety class.
require_status narrows to third_party_certified, self_declared or conforms_to. A class constraint never misses: a card without a matching certification is unknown, and unknown is excluded unless you include it.
A certification may carry a level as the scheme writes it (SIL 3, PL e, Gold). It is on the card, so read it from card.json or ask for include_cards. It is not a query field and not in the match summary, and it never changes the certification’s class.
Physical specs are the robotics extension
payload_kg, reach_mm, repeatability_mm, axes, ip_rating, cleanroom_class, mounting, integration_required, protocols, applications, compatible_robots and robot_brands come from the optional robotics extension. Only robotics and automation offerings carry it. A lathe, a tractor or a calibration service states none of them.
For those fields unknown is excluded by default, so one constraint on any of them removes every offering without the extension. A payload floor on machine_tool returns nothing today. Leave them off outside robotics, or keep the unknowns on that one constraint:
{"field": "payload_kg", "op": "at_least", "value": 10, "include_unknown": true}The offering then comes back with that constraint marked unknown_included instead of being dropped. The core fields are on every card: category, kind, role, channels, price, lead time, warranty, certifications and service area.
Service area
serves takes one region token: an ISO 3166-1 country (DE), an ISO 3166-2 subdivision (CA-ON), or a lowercase macro such as europe or north_america. It resolves against the card’s service_area, a core card-level field since 0.2. A 0.1 card states it in the robotics extension and is read from there; when both are present the core field wins. Containment runs both ways, so a card serving CA serves CA-ON. A token the vocabulary cannot place is refused with 400 rather than answered.
Response
{
"fx": {"source": "...", "as_of": "2026-09-29"},
"total": 7,
"total_offerings": 7,
"results": [{
"record": {
"domain": "universal-robots.com",
"status": "unclaimed",
"card_url": "https://itha.ai/c/universal-robots.com/card.json",
"provenance_url": "https://itha.ai/c/universal-robots.com/provenance.json",
"profile_url": "https://itha.ai/c/universal-robots.com"
},
"matches": [{
"offering_id": "ur-series",
"category": "cobot",
"citation": {"card_url": "...", "offering_id": "ur-series"},
"constraints": [
{"field": "payload_kg", "status": "matched"},
{"field": "price_usd", "status": "unknown_included"}
],
"notes": ["Robot arms are partly completed machines: no CE mark is affixed..."]
}]
}]
}notes carries the seller’s own caveats from terms.notes and pricing.notes. Surface them: they are exactly the facts a scraped answer loses.
Add "explain": "<domain>" to see every offering of one company with full three-valued statuses, including the ones that were excluded and why.
Documents
Trust and safety
MCP
The same surface is published as an MCP server, so an agent can plug the registry in as a tool rather than writing a client. It is a thin wrapper: every tool returns this API’s JSON untouched alongside a text rendering, so anything integrated against the shape above keeps working. The hosted endpoint is https://mcp.itha.ai/mcp.
# Claude Code, hosted
claude mcp add --transport http itha https://mcp.itha.ai/mcp
# Claude Code, local
claude mcp add itha -- node /path/to/itha/mcp/src/stdio.mjs --registry=https://itha.ai
# Claude Desktop: claude_desktop_config.json
{"mcpServers": {"itha": {"command": "node",
"args": ["/path/to/itha/mcp/src/stdio.mjs"],
"env": {"ITHA_REGISTRY_URL": "https://itha.ai"}}}}
# Messages API: both halves are required
{"mcp_servers": [{"type": "url", "url": "https://mcp.itha.ai/mcp", "name": "itha"}],
"tools": [{"type": "mcp_toolset", "mcp_server_name": "itha"}]}