{
  "openapi": "3.1.0",
  "info": {
    "title": "CNC Compass Search API",
    "version": "1.2.0",
    "summary": "Natural-language search over published CNC supplier records.",
    "description": "Public, read-only. Anonymous calls are rate limited per IP address; an optional API key in `X-Api-Key` (or `Authorization: Bearer`) raises the quota. Every fact in a result is also published as static HTML with JSON-LD at the supplier's page. The same search is exposed as the MCP tool `search_suppliers` at `/mcp`.",
    "license": {
      "name": "Data policy",
      "url": "/.well-known/ai-access.json"
    }
  },
  "servers": [
    {
      "url": "https://cnccompass.com",
      "description": "CNC Compass"
    }
  ],
  "paths": {
    "/api/search": {
      "get": {
        "operationId": "searchSuppliersGet",
        "summary": "Search suppliers (query string)",
        "description": "Same search as POST for clients that prefer a URL. `query` is accepted as an alias of `q`.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "A CNC sourcing brief in natural language: material, process, quantity, tolerance, finish, or location. Example: '500 black anodized 6061 aluminum housings, CNC milling, ±0.01 mm bores'.",
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 5000
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Records per page.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 20,
              "default": 5
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Records to skip.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000,
              "default": 0
            }
          },
          {
            "name": "material",
            "in": "query",
            "required": false,
            "description": "Overrides the material parsed from the query. Matched against the facet vocabulary; an unmatched value is ignored and named in retrieval.ignored_fields. Example: aluminum 6061.",
            "schema": {
              "type": "string",
              "maxLength": 80
            }
          },
          {
            "name": "process",
            "in": "query",
            "required": false,
            "description": "Overrides the process parsed from the query. Matched against the facet vocabulary; an unmatched value is ignored and named in retrieval.ignored_fields. Example: CNC turning.",
            "schema": {
              "type": "string",
              "maxLength": 80
            }
          },
          {
            "name": "finish",
            "in": "query",
            "required": false,
            "description": "Overrides the finish parsed from the query. Matched against the facet vocabulary; an unmatched value is ignored and named in retrieval.ignored_fields. Example: anodizing.",
            "schema": {
              "type": "string",
              "maxLength": 80
            }
          },
          {
            "name": "location",
            "in": "query",
            "required": false,
            "description": "Overrides the location parsed from the query. Matched against the facet vocabulary; an unmatched value is ignored and named in retrieval.ignored_fields. Example: Dongguan.",
            "schema": {
              "type": "string",
              "maxLength": 80
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ranked supplier records for the brief.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid query, limit, or offset.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "Request body larger than 16 KB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "415": {
            "description": "POST body is not JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit reached for this IP address or API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Search service not configured or unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          },
          {}
        ]
      },
      "post": {
        "operationId": "searchSuppliers",
        "summary": "Search suppliers (JSON body)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ranked supplier records for the brief.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid query, limit, or offset.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "Request body larger than 16 KB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "415": {
            "description": "POST body is not JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit reached for this IP address or API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Search service not configured or unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          },
          {}
        ]
      }
    },
    "/mcp": {
      "post": {
        "operationId": "mcp",
        "summary": "MCP server (Streamable HTTP, stateless, JSON responses)",
        "description": "Model Context Protocol endpoint exposing the tool `search_suppliers` (query, limit, offset, and optional material, process, finish, location). Send JSON-RPC requests with `Accept: application/json, text/event-stream`. No session is required.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit reached for this IP address or API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Search service not configured or unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "apiKey": []
          },
          {}
        ]
      }
    }
  },
  "components": {
    "schemas": {
      "AppliedFilter": {
        "description": "One brief field used to filter records, with how many index records declare it.",
        "properties": {
          "field": {
            "title": "Field",
            "type": "string"
          },
          "label": {
            "title": "Label",
            "type": "string"
          },
          "keys": {
            "items": {
              "type": "string"
            },
            "title": "Keys",
            "type": "array"
          },
          "records": {
            "default": 0,
            "title": "Records",
            "type": "integer"
          }
        },
        "required": [
          "field",
          "label"
        ],
        "title": "AppliedFilter",
        "type": "object"
      },
      "Evidence": {
        "properties": {
          "label": {
            "title": "Label",
            "type": "string"
          },
          "quote": {
            "title": "Quote",
            "type": "string"
          },
          "source_url": {
            "default": "",
            "title": "Source Url",
            "type": "string"
          }
        },
        "required": [
          "label",
          "quote"
        ],
        "title": "Evidence",
        "type": "object"
      },
      "SearchBrief": {
        "properties": {
          "material": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Material"
          },
          "process": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Process"
          },
          "finish": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Finish"
          },
          "tolerance": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Tolerance"
          },
          "quantity": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Quantity"
          },
          "location": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Location"
          }
        },
        "title": "SearchBrief",
        "type": "object"
      },
      "SearchRequest": {
        "properties": {
          "query": {
            "maxLength": 5000,
            "minLength": 3,
            "title": "Query",
            "type": "string",
            "description": "A CNC sourcing brief in natural language: material, process, quantity, tolerance, finish, or location. Example: '500 black anodized 6061 aluminum housings, CNC milling, ±0.01 mm bores'."
          },
          "limit": {
            "default": 5,
            "maximum": 20,
            "minimum": 1,
            "title": "Limit",
            "type": "integer",
            "description": "Records per page."
          },
          "offset": {
            "default": 0,
            "maximum": 1000,
            "minimum": 0,
            "title": "Offset",
            "type": "integer",
            "description": "Records to skip. Resend the same query with the previous offset plus page length."
          },
          "material": {
            "anyOf": [
              {
                "maxLength": 80,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Optional. Overrides the material parsed from the query; matched against the facet vocabulary, an unmatched value is ignored and named in retrieval.ignored_fields. Example: aluminum 6061.",
            "title": "Material"
          },
          "process": {
            "anyOf": [
              {
                "maxLength": 80,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Optional. Overrides the process parsed from the query; matched against the facet vocabulary, an unmatched value is ignored and named in retrieval.ignored_fields. Example: CNC turning.",
            "title": "Process"
          },
          "finish": {
            "anyOf": [
              {
                "maxLength": 80,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Optional. Overrides the finish parsed from the query; matched against the facet vocabulary, an unmatched value is ignored and named in retrieval.ignored_fields. Example: anodizing.",
            "title": "Finish"
          },
          "location": {
            "anyOf": [
              {
                "maxLength": 80,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Optional. Overrides the location parsed from the query; matched against the facet vocabulary, an unmatched value is ignored and named in retrieval.ignored_fields. Example: Dongguan.",
            "title": "Location"
          }
        },
        "required": [
          "query"
        ],
        "title": "SearchRequest",
        "type": "object"
      },
      "SearchResponse": {
        "properties": {
          "answer": {
            "title": "Answer",
            "type": "string"
          },
          "brief": {
            "$ref": "#/components/schemas/SearchBrief"
          },
          "missing": {
            "items": {
              "type": "string"
            },
            "title": "Missing",
            "type": "array"
          },
          "follow_up_question": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Follow Up Question"
          },
          "suppliers": {
            "items": {
              "$ref": "#/components/schemas/SupplierHit"
            },
            "title": "Suppliers",
            "type": "array"
          },
          "filters": {
            "items": {
              "$ref": "#/components/schemas/AppliedFilter"
            },
            "title": "Filters",
            "type": "array"
          },
          "offset": {
            "default": 0,
            "title": "Offset",
            "type": "integer"
          },
          "limit": {
            "default": 5,
            "title": "Limit",
            "type": "integer"
          },
          "has_more": {
            "default": false,
            "title": "Has More",
            "type": "boolean"
          },
          "total_records": {
            "default": 0,
            "title": "Total Records",
            "type": "integer"
          },
          "index_records": {
            "default": 0,
            "title": "Index Records",
            "type": "integer"
          },
          "retrieval": {
            "additionalProperties": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "title": "Retrieval",
            "type": "object"
          }
        },
        "required": [
          "answer",
          "brief"
        ],
        "title": "SearchResponse",
        "type": "object"
      },
      "SupplierHit": {
        "properties": {
          "slug": {
            "title": "Slug",
            "type": "string"
          },
          "name": {
            "title": "Name",
            "type": "string"
          },
          "location": {
            "default": "",
            "title": "Location",
            "type": "string"
          },
          "score": {
            "title": "Score",
            "type": "number"
          },
          "match_reasons": {
            "items": {
              "type": "string"
            },
            "title": "Match Reasons",
            "type": "array"
          },
          "matched_fields": {
            "items": {
              "type": "string"
            },
            "title": "Matched Fields",
            "type": "array"
          },
          "website_url": {
            "default": "",
            "title": "Website Url",
            "type": "string"
          },
          "website_kind": {
            "default": "",
            "title": "Website Kind",
            "type": "string"
          },
          "factory_verification": {
            "default": "",
            "title": "Factory Verification",
            "type": "string"
          },
          "inquiry_url": {
            "default": "",
            "title": "Inquiry Url",
            "type": "string"
          },
          "evidence": {
            "items": {
              "$ref": "#/components/schemas/Evidence"
            },
            "title": "Evidence",
            "type": "array"
          }
        },
        "required": [
          "slug",
          "name",
          "score"
        ],
        "title": "SupplierHit",
        "type": "object"
      },
      "ErrorResponse": {
        "type": "object",
        "title": "ErrorResponse",
        "required": [
          "ok",
          "error"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string",
            "description": "Human-readable reason the request was rejected."
          }
        }
      }
    },
    "securitySchemes": {
      "apiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "Optional. Raises the anonymous per-IP quota."
      }
    }
  }
}