{
  "openapi": "3.1.0",
  "info": {
    "title": "hausfold.co",
    "summary": "Public endpoints for hausfold's Mac software: installers, release metadata, docs search, a versioned REST surface, and an MCP endpoint for coding agents.",
    "description": "Everything here is public. No API keys, no accounts, nothing to buy. hausfold makes Mac software: haus, a layer that rebuilds a whole Mac from one text file, and small apps that run on it (and without it). See https://hausfold.co/developers/ for prose examples and the MCP tool list.\n\nVersioning and deprecation policy: /v1 is path-versioned. A deprecated endpoint keeps answering and adds a 'Deprecation: true' header plus a 'Sunset' header carrying the date it stops, announced before the date. The un-versioned endpoints (installers, /api/release/{app}, /download/{app}, /api/search, /mcp, /ask, /design.md, the llms.txt files) are the long-lived surface and do not carry a version, because they have never changed shape.\n\nAuthentication: none. There is no OAuth authorization server behind this host; see https://hausfold.co/auth.md for the full account of how agents authenticate (they don't). Errors are RFC 9457 problem+json with a machine-readable 'code' field. Rate limiting is per client IP and per edge node, so treat the RateLimit headers as an approximation.\n\nAgent discovery documents: /mcp.json (the agent-plugins.org manifest naming both MCP transports), /.well-known/oauth-protected-resource (RFC 9728; the resource is public, so authorization_servers is empty), /.well-known/http-message-signatures-directory (Web Bot Auth keys; empty, because this host signs no responses), and /.well-known/mcp/server-card.json. See /auth.md for the full account.\n\nSandbox: every /v1 data read (search, desktops, apps, releases) accepts sandbox=true (and POST /v1/batch accepts a top-level \"sandbox\": true flag), returning deterministic sample payloads with no live GitHub lookups, for exercising a client against the documented shapes.",
    "version": "1.0.0",
    "contact": {
      "name": "hausfold",
      "email": "julien@hausfold.co",
      "url": "https://hausfold.co/developers/"
    },
    "license": {
      "name": "MIT",
      "url": "https://github.com/hausfold/haus#readme"
    }
  },
  "servers": [
    {
      "url": "https://hausfold.co"
    }
  ],
  "tags": [
    {
      "name": "install",
      "description": "The install scripts and their pinning."
    },
    {
      "name": "releases",
      "description": "Latest release metadata and download redirects."
    },
    {
      "name": "docs",
      "description": "Documentation in machine-readable shapes: search, llms.txt, the ask endpoint."
    },
    {
      "name": "mcp",
      "description": "The Model Context Protocol endpoint and its metadata."
    },
    {
      "name": "v1",
      "description": "The versioned REST surface: paginated search, batch, jobs."
    }
  ],
  "paths": {
    "/v1/search": {
      "get": {
        "summary": "Full-text search of the documentation, cursor-paginated",
        "description": "Scores the same index /api/search serves (one entry per page section) and returns ranked excerpts. Page with 'cursor' until next_cursor is null.",
        "operationId": "searchDocs",
        "tags": [
          "v1",
          "docs"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/q"
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/cursor"
          },
          {
            "$ref": "#/components/parameters/sandbox"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of ranked search results.",
            "headers": {
              "RateLimit-Limit": {
                "description": "The per-window request ceiling.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "What is left of the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window turns.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/searchResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/badRequest"
          },
          "429": {
            "$ref": "#/components/responses/rateLimited"
          },
          "503": {
            "$ref": "#/components/responses/indexUnavailable"
          }
        }
      }
    },
    "/v1/desktops": {
      "get": {
        "summary": "Every installable desktop and the URL that installs it",
        "description": "One row per desktop. 'pins' is the desktop the URL pre-answers, or null for /haus.sh, which installs the layer and asks.",
        "operationId": "listDesktops",
        "tags": [
          "v1",
          "install"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/cursor"
          },
          {
            "$ref": "#/components/parameters/sandbox"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of desktops.",
            "headers": {
              "RateLimit-Limit": {
                "description": "The per-window request ceiling.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "What is left of the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window turns.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/desktopPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/badRequest"
          },
          "429": {
            "$ref": "#/components/responses/rateLimited"
          }
        }
      }
    },
    "/v1/apps": {
      "get": {
        "summary": "Every app with signed macOS releases",
        "description": "One row per downloadable app, with the URLs for its release metadata and stable download redirect.",
        "operationId": "listApps",
        "tags": [
          "v1",
          "releases"
        ],
        "responses": {
          "200": {
            "description": "A page of apps.",
            "headers": {
              "RateLimit-Limit": {
                "description": "The per-window request ceiling.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "What is left of the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window turns.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/appPage"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/rateLimited"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/sandbox"
          }
        ]
      }
    },
    "/v1/releases/{app}": {
      "get": {
        "summary": "Latest release metadata for an app",
        "description": "Small JSON document (tag, asset, size, url, publishedAt), cached at the edge for five minutes.",
        "operationId": "getRelease",
        "tags": [
          "v1",
          "releases"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/app"
          },
          {
            "$ref": "#/components/parameters/sandbox"
          }
        ],
        "responses": {
          "200": {
            "description": "Release metadata.",
            "headers": {
              "RateLimit-Limit": {
                "description": "The per-window request ceiling.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "What is left of the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window turns.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/release"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/unknownApp"
          },
          "429": {
            "$ref": "#/components/responses/rateLimited"
          },
          "502": {
            "$ref": "#/components/responses/upstreamUnavailable"
          }
        }
      }
    },
    "/v1/batch": {
      "post": {
        "summary": "Run several read operations in one request",
        "description": "Takes an array of operations (search, release, install) and answers with one result per operation, in order. Per-operation failures come back as ok:false entries, not as a failed request. For more than 20 operations, use POST /v1/jobs. Supports the Idempotency-Key header: the first response is remembered for 24h and a retry with the same key is answered from memory with Idempotency-Replayed: true.",
        "operationId": "batchOperations",
        "tags": [
          "v1"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "A client-chosen token (letters, digits, dots, underscores, hyphens; at most 200 characters). The response to the first request carrying this key is reused for every retry with the same key for 24 hours, so a network retry cannot double-apply work.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9._-]{1,200}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/batchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One result per operation, in order.",
            "headers": {
              "Idempotency-Replayed": {
                "description": "true when this response was served from the Idempotency-Key memory rather than re-executed.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                }
              },
              "RateLimit-Limit": {
                "description": "The per-window request ceiling.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "What is left of the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window turns.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/batchResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/badRequest"
          },
          "429": {
            "$ref": "#/components/responses/rateLimited"
          }
        }
      }
    },
    "/v1/jobs": {
      "post": {
        "summary": "Run a batch of operations asynchronously",
        "description": "Same body as POST /v1/batch, but answered immediately with 202 and a Location to poll. The job result is kept for an hour.",
        "operationId": "createJob",
        "tags": [
          "v1"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/batchRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The job was queued. Poll the Location (also given in the body as 'url'); a Retry-After of one second suggests the first poll.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "format": "uri-reference"
                }
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Limit": {
                "description": "The per-window request ceiling.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "What is left of the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window turns.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/jobStatus"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/badRequest"
          },
          "429": {
            "$ref": "#/components/responses/rateLimited"
          }
        }
      }
    },
    "/v1/jobs/{id}": {
      "get": {
        "summary": "Poll an async job",
        "description": "Returns the job's current status ('queued' or 'done'; 'failed' only if the job itself crashed) and, once done, its result: one entry per operation, the same shape POST /v1/batch would have answered synchronously.",
        "operationId": "getJob",
        "tags": [
          "v1"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The job id from the 202 response.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The job's status, with the result once done.",
            "headers": {
              "RateLimit-Limit": {
                "description": "The per-window request ceiling.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "What is left of the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window turns.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/jobStatus"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/unknownJob"
          },
          "429": {
            "$ref": "#/components/responses/rateLimited"
          }
        }
      }
    },
    "/ask": {
      "get": {
        "summary": "Natural-language query over the documentation (NLWeb shape)",
        "description": "One endpoint to ask this site a question: it answers with ranked doc excerpts under '_meta' plus 'results', the NLWeb response shape. Add streaming=true (or an Accept: text/event-stream) for SSE with start, result and complete events. This is docs search, not a chat model.",
        "operationId": "askGet",
        "tags": [
          "docs"
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "The natural-language query.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "streaming",
            "in": "query",
            "required": false,
            "description": "true switches the response to text/event-stream (SSE).",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Search results under the NLWeb envelope, or an SSE stream when streaming was requested.",
            "headers": {
              "RateLimit-Limit": {
                "description": "The per-window request ceiling.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "What is left of the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window turns.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/askResponse"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/badRequest"
          },
          "405": {
            "$ref": "#/components/responses/methodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/rateLimited"
          },
          "503": {
            "$ref": "#/components/responses/indexUnavailable"
          }
        }
      },
      "post": {
        "summary": "Natural-language query over the documentation, body form",
        "description": "Same as GET /ask with the query in the body. Set prefer.streaming (or send Accept: text/event-stream) for the SSE form.",
        "operationId": "askPost",
        "tags": [
          "docs"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The natural-language query."
                  },
                  "prefer": {
                    "type": "object",
                    "properties": {
                      "streaming": {
                        "type": "boolean",
                        "default": false
                      }
                    }
                  }
                },
                "required": [
                  "query"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Search results under the NLWeb envelope, or an SSE stream when streaming was requested.",
            "headers": {
              "RateLimit-Limit": {
                "description": "The per-window request ceiling.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "What is left of the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window turns.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/askResponse"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/badRequest"
          },
          "429": {
            "$ref": "#/components/responses/rateLimited"
          },
          "503": {
            "$ref": "#/components/responses/indexUnavailable"
          }
        }
      }
    },
    "/haus.sh": {
      "get": {
        "summary": "Install script for the haus layer; asks which desktop to build",
        "description": "Proxies haus's bootstrap.sh with nothing pinned, so the installer interviews you. Run it with `curl -fsSL https://hausfold.co/haus.sh | bash`.",
        "operationId": "getHausInstaller",
        "tags": [
          "install"
        ],
        "parameters": [
          {
            "name": "ref",
            "in": "query",
            "description": "Pin the bootstrap.sh source to an exact haus release tag (vYYYY.MM.DD or vYYYY.MM.DD-N). Unpublished: main is the default.",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^v[0-9]{4}\\.[0-9]{2}\\.[0-9]{2}(-[0-9]+)?$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The installer as plain text, pinned via HAUS_DESKTOP. `x-hausfold-ref` names the ref it was fetched from.",
            "headers": {
              "x-hausfold-ref": {
                "description": "The ref the script was proxied from (a release tag, or main before the first release).",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "text/plain": {}
            }
          },
          "400": {
            "description": "A ref that is not a release tag was requested.",
            "content": {
              "text/plain": {}
            }
          },
          "502": {
            "description": "The upstream script could not be fetched.",
            "content": {
              "text/plain": {}
            }
          }
        }
      }
    },
    "/hacker.sh": {
      "get": {
        "summary": "Install script for the hacker desktop, pinned by URL",
        "description": "Same script as /haus.sh with the hacker desktop pinned, so the installer does not ask. `x-hausfold-desktop` echoes the pin.",
        "operationId": "getHackerInstaller",
        "tags": [
          "install"
        ],
        "responses": {
          "200": {
            "description": "The installer as plain text.",
            "headers": {
              "x-hausfold-ref": {
                "schema": {
                  "type": "string"
                }
              },
              "x-hausfold-desktop": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "text/plain": {}
            }
          }
        }
      }
    },
    "/everyday.sh": {
      "get": {
        "summary": "Install script for the everyday desktop, pinned by URL",
        "description": "Same shape as /hacker.sh for the everyday desktop.",
        "operationId": "getEverydayInstaller",
        "tags": [
          "install"
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/installer"
          },
          "400": {
            "description": "A ref that is not a release tag was requested.",
            "content": {
              "text/plain": {}
            }
          },
          "502": {
            "description": "The upstream script could not be fetched.",
            "content": {
              "text/plain": {}
            }
          }
        }
      }
    },
    "/minimal.sh": {
      "get": {
        "summary": "Install script for the minimal desktop, pinned by URL",
        "description": "Same shape as /hacker.sh for the minimal desktop.",
        "operationId": "getMinimalInstaller",
        "tags": [
          "install"
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/installer"
          },
          "400": {
            "description": "A ref that is not a release tag was requested.",
            "content": {
              "text/plain": {}
            }
          },
          "502": {
            "description": "The upstream script could not be fetched.",
            "content": {
              "text/plain": {}
            }
          }
        }
      }
    },
    "/download/{app}": {
      "get": {
        "summary": "Redirect to the latest macOS release artifact of an app",
        "description": "302 to the most-preferred macOS asset of the app's latest GitHub release (DMG preferred over the archive). GitHub keeps hosting the bytes and counting downloads.",
        "operationId": "redirectDownload",
        "tags": [
          "releases"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/app"
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to the asset's download URL, or to the repo's releases page when no signed artifact is found.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "404": {
            "description": "The app slug is not one of the downloadable apps. A non-HTML request is answered with a real 404 whose body is markdown naming where to look (llms.txt, the docs, openapi.json); a browser gets the site's usual 404 page.",
            "content": {
              "text/markdown": {}
            }
          }
        }
      }
    },
    "/api/release/{app}": {
      "get": {
        "summary": "Latest release metadata for an app (un-versioned alias of /v1/releases/{app})",
        "description": "The long-lived form of the same document /v1/releases/{app} serves. Kept because it shipped first and is in print.",
        "operationId": "getReleaseLegacy",
        "tags": [
          "releases"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/app"
          }
        ],
        "responses": {
          "200": {
            "description": "Release metadata.",
            "headers": {
              "RateLimit-Limit": {
                "description": "The per-window request ceiling.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "What is left of the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window turns.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/release"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/unknownApp"
          },
          "429": {
            "$ref": "#/components/responses/rateLimited"
          },
          "502": {
            "$ref": "#/components/responses/upstreamUnavailable"
          }
        }
      }
    },
    "/design.md": {
      "get": {
        "summary": "The family's visual standard as one public markdown URL",
        "description": "Proxied from the workshop repo's docs/design.md. Intended for a coding agent to load before drawing something that carries the brand.",
        "operationId": "getDesignStandard",
        "tags": [
          "docs"
        ],
        "responses": {
          "200": {
            "description": "Markdown.",
            "content": {
              "text/markdown": {}
            }
          },
          "502": {
            "description": "The upstream file could not be fetched.",
            "content": {
              "text/plain": {}
            }
          }
        }
      }
    },
    "/auth.md": {
      "get": {
        "summary": "How agents authenticate (they don't), per the auth.md convention",
        "description": "Markdown. States the one supported method (anonymous), and what this host deliberately does not implement (no OAuth authorization server, no identity assertions). See https://github.com/workos/auth.md for the convention.",
        "operationId": "getAuthMd",
        "tags": [
          "docs"
        ],
        "responses": {
          "200": {
            "description": "Markdown.",
            "content": {
              "text/markdown": {}
            }
          }
        }
      }
    },
    "/api/search": {
      "get": {
        "summary": "The full-text search index of the documentation",
        "description": "The complete search index (Orama JSON format) behind /docs: one entry per page section with url, breadcrumbs and content. search_docs on /mcp and GET /v1/search both score this same index.",
        "operationId": "getSearchIndex",
        "tags": [
          "docs"
        ],
        "responses": {
          "200": {
            "description": "Orama search index.",
            "content": {
              "application/json": {}
            }
          }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "summary": "Documentation as markdown for LLMs, index form",
        "description": "One entry per docs page. Plain text, no runtime.",
        "operationId": "getLlmsIndex",
        "tags": [
          "docs"
        ],
        "responses": {
          "200": {
            "description": "Markdown index.",
            "content": {
              "text/plain": {}
            }
          }
        }
      }
    },
    "/llms-full.txt": {
      "get": {
        "summary": "Documentation as markdown for LLMs, full text",
        "description": "Every docs page's full text in one file. Plain text, no runtime.",
        "operationId": "getLlmsFull",
        "tags": [
          "docs"
        ],
        "responses": {
          "200": {
            "description": "Markdown, full text.",
            "content": {
              "text/plain": {}
            }
          }
        }
      }
    },
    "/.well-known/mcp/server-card.json": {
      "get": {
        "summary": "The MCP server card",
        "description": "Generated from the same tool table /mcp serves, so the two cannot drift, and its name and version are serverInfo's. Lets an agent preview the server before opening a transport connection. Also served at /mcp/server-card.",
        "operationId": "getMcpServerCard",
        "tags": [
          "mcp"
        ],
        "responses": {
          "200": {
            "description": "The server card.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/serverCard"
                }
              }
            }
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "summary": "Model Context Protocol endpoint",
        "description": "JSON-RPC 2.0 over Streamable HTTP. Stateless: no session ids, and GET is refused (405) because there is nothing to stream. Open CORS, no authentication. Initialize, then list or call tools; every tool reads public data and carries readOnly annotations. A docs-only transport serving search_docs alone runs at /mcp/docs. Tool errors come back as isError results whose text and structuredContent carry {error: {code, message}}. Responses carry the RateLimit trio.",
        "operationId": "mcpPost",
        "tags": [
          "mcp"
        ],
        "parameters": [
          {
            "name": "mcp-protocol-version",
            "in": "header",
            "required": false,
            "description": "Optional. Informational for this server: negotiation happens in the initialize request's protocolVersion parameter; the response's mcp-protocol-version header names the version the server answered with.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/jsonRpcRequest"
                  },
                  {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/jsonRpcRequest"
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response (single object, or array for a batch).",
            "headers": {
              "mcp-protocol-version": {
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "The per-window request ceiling.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "What is left of the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window turns.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/jsonRpcResponse"
                }
              }
            }
          },
          "202": {
            "description": "The request (or every message in the batch) was a notification; there is nothing to reply."
          },
          "400": {
            "description": "Body was not JSON (JSON-RPC -32700 parse error).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/jsonRpcError"
                }
              }
            }
          },
          "405": {
            "description": "Method not POST. GET would open an SSE stream; this server has nothing to push.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/jsonRpcError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/rateLimited"
          }
        }
      },
      "options": {
        "summary": "CORS preflight",
        "description": "Open preflight: any origin, POST, and the MCP headers.",
        "operationId": "mcpPreflight",
        "tags": [
          "mcp"
        ],
        "responses": {
          "204": {
            "description": "Preflight allowed."
          }
        }
      }
    },
    "/mcp/docs": {
      "post": {
        "summary": "Model Context Protocol endpoint, documentation only",
        "description": "The docs half of the MCP surface as its own transport: search_docs alone, against the same index /v1/search scores. Stateless JSON-RPC 2.0 over Streamable HTTP, open CORS, no authentication. Use this transport when the agent only wants to read the docs; use /mcp for install commands and release metadata too. The manifest naming both servers is at /mcp.json.",
        "operationId": "mcpDocsPost",
        "tags": [
          "mcp",
          "docs"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/jsonRpcRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response.",
            "headers": {
              "RateLimit-Limit": {
                "description": "The per-window request ceiling.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "What is left of the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window turns.",
                "schema": {
                  "type": "integer"
                }
              },
              "mcp-protocol-version": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/jsonRpcResponse"
                }
              }
            }
          },
          "400": {
            "description": "Body was not JSON (JSON-RPC -32700 parse error).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/jsonRpcError"
                }
              }
            }
          },
          "405": {
            "description": "Method not POST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/jsonRpcError"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/rateLimited"
          }
        }
      },
      "options": {
        "summary": "CORS preflight",
        "description": "Open preflight: any origin, POST, and the MCP headers.",
        "operationId": "mcpDocsPreflight",
        "tags": [
          "mcp",
          "docs"
        ],
        "responses": {
          "204": {
            "description": "Preflight allowed."
          }
        }
      }
    },
    "/mcp.json": {
      "get": {
        "summary": "The agent-plugins.org MCP manifest",
        "description": "Names both MCP transports (hausfold at /mcp, hausfold-docs at /mcp/docs) with their URLs, so a manifest probe discovers the servers without reading the docs first.",
        "operationId": "getMcpManifest",
        "tags": [
          "mcp"
        ],
        "responses": {
          "200": {
            "description": "The manifest.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mcpManifest"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/oauth-protected-resource": {
      "get": {
        "summary": "RFC 9728 Protected Resource Metadata",
        "description": "Describes how an agent authenticates to this resource: it does not have to. resource names this host, resource_documentation points at /auth.md, authorization_servers is empty because no authorization server stands behind the resource, and no scopes are required. Published so discovery never walks into a 404; if an endpoint ever starts requiring credentials, its 401 will advertise this URL in WWW-Authenticate.",
        "operationId": "getProtectedResourceMetadata",
        "tags": [
          "docs"
        ],
        "responses": {
          "200": {
            "description": "The metadata document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/protectedResource"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/http-message-signatures-directory": {
      "get": {
        "summary": "Web Bot Auth directory of response-signing keys",
        "description": "The Ed25519 keys this host signs its HTTP responses with, per the Web Bot Auth draft. hausfold.co signs no responses, so the keys array is empty. An empty directory is the honest statement of that; a fabricated key would be worse than none.",
        "operationId": "getSignatureDirectory",
        "tags": [
          "docs"
        ],
        "responses": {
          "200": {
            "description": "The directory.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/signatureDirectory"
                }
              }
            }
          }
        }
      }
    },
    "/index.md": {
      "get": {
        "summary": "The agent view: this domain in one markdown page",
        "description": "Answers what the domain is for, when an agent should call it, and with what: endpoints, auth (none), and per-desktop install commands. Also served for ?mode=agent on /, for Accept: text/markdown, and to AI-bot User-Agents that request /. Varies on Accept and User-Agent; not cached.",
        "responses": {
          "200": {
            "description": "Markdown.",
            "content": {
              "text/markdown": {}
            }
          }
        },
        "operationId": "getIndexMd"
      }
    },
    "/docs/{path}.md": {
      "get": {
        "summary": "A docs page's markdown twin",
        "description": "The processed markdown of any docs page, re-served from the page's own URL plus .md (e.g. /docs/haus/install.md). Byte-for-byte the same file /llms.mdx/docs/<path>/content.md carries.",
        "parameters": [
          {
            "name": "path",
            "in": "path",
            "required": true,
            "description": "The page's path under /docs, without the .md suffix, e.g. haus/install.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Markdown.",
            "content": {
              "text/markdown": {}
            }
          },
          "404": {
            "description": "No docs page at that path."
          }
        },
        "operationId": "getDocsMarkdownTwin"
      }
    },
    "/.well-known/api-catalog": {
      "get": {
        "summary": "API catalog (RFC 9727)",
        "description": "A linkset pointing at the OpenAPI service description, the MCP endpoint, and the docs surfaces. Served with the RFC's profile parameter on the content type.",
        "responses": {
          "200": {
            "description": "The linkset.",
            "content": {
              "application/linkset+json": {}
            }
          }
        },
        "operationId": "getApiCatalog"
      }
    },
    "/.well-known/agent-card.json": {
      "get": {
        "summary": "A2A agent card",
        "description": "Agent2Agent discovery manifest describing this domain's agentic surface: the MCP endpoint as its URL, three skills (search-docs, install-command, latest-release), and the docs as documentationUrl. No authentication.",
        "responses": {
          "200": {
            "description": "The agent card.",
            "content": {
              "application/json": {}
            }
          }
        },
        "operationId": "getAgentCard"
      }
    },
    "/.well-known/agent-skills/index.json": {
      "get": {
        "summary": "Agent Skills discovery index",
        "description": "Index of this domain's agent skills per the Agent Skills Discovery draft: hausfold-docs-search, hausfold-install, hausfold-releases. Each entry carries the SHA-256 digest of its SKILL.md, generated at build time by scripts/gen-agent-skills.mjs.",
        "responses": {
          "200": {
            "description": "The discovery index.",
            "content": {
              "application/json": {}
            }
          }
        },
        "operationId": "getAgentSkillsIndex"
      }
    },
    "/sitemap.xml": {
      "get": {
        "summary": "XML sitemap",
        "description": "Every indexable URL: the hand-written pages and the docs, trailing slash included. Generated at build time from the same page table the site renders from.",
        "responses": {
          "200": {
            "description": "The sitemap.",
            "content": {
              "application/xml": {}
            }
          }
        },
        "operationId": "getSitemap"
      }
    },
    "/mcp/server-card": {
      "get": {
        "summary": "MCP Server Card (SEP-2127 recommended location)",
        "description": "The same document as /.well-known/mcp/server-card.json, served at the location SEP-2127 reserves: the streamable-HTTP URL plus /server-card.",
        "responses": {
          "200": {
            "description": "The server card.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/serverCard"
                }
              }
            }
          }
        },
        "operationId": "getMcpServerCardRecommended"
      }
    }
  },
  "components": {
    "responses": {
      "installer": {
        "description": "The installer as plain text, pinned via HAUS_DESKTOP. `x-hausfold-ref` names the ref it was fetched from and `x-hausfold-desktop` echoes the pin.",
        "headers": {
          "x-hausfold-ref": {
            "description": "The ref the script was proxied from (a release tag, or main before the first release).",
            "schema": {
              "type": "string"
            }
          },
          "x-hausfold-desktop": {
            "description": "The desktop this URL pinned. Absent on /haus.sh, which pins nothing.",
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "text/plain": {}
        }
      },
      "badRequest": {
        "description": "A parameter was missing or malformed.",
        "headers": {
          "RateLimit-Limit": {
            "description": "The per-window request ceiling.",
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Remaining": {
            "description": "What is left of the current window.",
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Reset": {
            "description": "Seconds until the window turns.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/problem"
            }
          }
        }
      },
      "unknownApp": {
        "description": "The app slug is not one of the downloadable apps. The problem detail names the ones that are.",
        "headers": {
          "RateLimit-Limit": {
            "description": "The per-window request ceiling.",
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Remaining": {
            "description": "What is left of the current window.",
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Reset": {
            "description": "Seconds until the window turns.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/problem"
            }
          }
        }
      },
      "unknownJob": {
        "description": "No such job, or its result aged out (results are kept for an hour).",
        "headers": {
          "RateLimit-Limit": {
            "description": "The per-window request ceiling.",
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Remaining": {
            "description": "What is left of the current window.",
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Reset": {
            "description": "Seconds until the window turns.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/problem"
            }
          }
        }
      },
      "rateLimited": {
        "description": "The rate-limit window was exceeded. Retry after the indicated seconds.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Limit": {
            "description": "The per-window request ceiling.",
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Remaining": {
            "description": "What is left of the current window.",
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Reset": {
            "description": "Seconds until the window turns.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/problem"
            }
          }
        }
      },
      "upstreamUnavailable": {
        "description": "GitHub's release API could not be reached.",
        "headers": {
          "RateLimit-Limit": {
            "description": "The per-window request ceiling.",
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Remaining": {
            "description": "What is left of the current window.",
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Reset": {
            "description": "Seconds until the window turns.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/problem"
            }
          }
        }
      },
      "indexUnavailable": {
        "description": "The docs search index could not be loaded.",
        "headers": {
          "RateLimit-Limit": {
            "description": "The per-window request ceiling.",
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Remaining": {
            "description": "What is left of the current window.",
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Reset": {
            "description": "Seconds until the window turns.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/problem"
            }
          }
        }
      },
      "methodNotAllowed": {
        "description": "The path exists but not for this method.",
        "headers": {
          "Allow": {
            "schema": {
              "type": "string"
            }
          },
          "RateLimit-Limit": {
            "description": "The per-window request ceiling.",
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Remaining": {
            "description": "What is left of the current window.",
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Reset": {
            "description": "Seconds until the window turns.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/problem"
            }
          }
        }
      },
      "notFound": {
        "description": "No endpoint answers this method and path.",
        "headers": {
          "RateLimit-Limit": {
            "description": "The per-window request ceiling.",
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Remaining": {
            "description": "What is left of the current window.",
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Reset": {
            "description": "Seconds until the window turns.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/problem"
            }
          }
        }
      }
    },
    "parameters": {
      "app": {
        "name": "app",
        "in": "path",
        "required": true,
        "description": "An app with signed, notarized macOS releases on GitHub.",
        "schema": {
          "type": "string",
          "enum": [
            "pounce",
            "perch"
          ]
        }
      },
      "q": {
        "name": "q",
        "in": "query",
        "required": true,
        "description": "The search query.",
        "schema": {
          "type": "string",
          "minLength": 1
        }
      },
      "limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Page size.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 50,
          "default": 10
        }
      },
      "cursor": {
        "name": "cursor",
        "in": "query",
        "required": false,
        "description": "The next_cursor value from the previous page. Omit (or leave empty) for the first page.",
        "schema": {
          "type": "string"
        }
      },
      "sandbox": {
        "name": "sandbox",
        "in": "query",
        "required": false,
        "description": "sandbox=true returns deterministic sample payloads: no live GitHub release lookups, no search-index dependency, byte-identical answers every call. For exercising a client against the documented shapes. The rate limit still applies.",
        "schema": {
          "type": "string",
          "enum": [
            "true",
            "1",
            "yes"
          ]
        }
      }
    },
    "schemas": {
      "problem": {
        "type": "object",
        "description": "RFC 9457 problem+json. 'code' is the machine-readable half an agent branches on; 'type' resolves on this host.",
        "properties": {
          "type": {
            "type": "string",
            "format": "uri-reference"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "description": "Stable identifier for the failure kind: missing_query, invalid_cursor, unknown_app, unknown_job, invalid_json, invalid_batch, batch_too_large, job_too_large, rate_limited, upstream_unavailable, index_unavailable, method_not_allowed, not_found."
          }
        },
        "required": [
          "type",
          "title",
          "status",
          "detail",
          "code"
        ]
      },
      "release": {
        "type": "object",
        "properties": {
          "tag": {
            "type": "string",
            "examples": [
              "v2026.08.14"
            ]
          },
          "asset": {
            "type": "string",
            "examples": [
              "pounce-2026.08.14-macos.dmg"
            ]
          },
          "size": {
            "type": "integer",
            "description": "Asset size in bytes."
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "publishedAt": {
            "type": "string",
            "format": "date-time"
          },
          "sandbox": {
            "type": "boolean",
            "description": "Present and true when the response is a sandbox fixture rather than a live release lookup."
          }
        },
        "required": [
          "tag",
          "asset",
          "size",
          "url",
          "publishedAt"
        ]
      },
      "searchHit": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri-reference"
          },
          "breadcrumbs": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "excerpt": {
            "type": "string"
          },
          "score": {
            "type": "integer"
          }
        },
        "required": [
          "url",
          "breadcrumbs",
          "excerpt",
          "score"
        ]
      },
      "searchResponse": {
        "type": "object",
        "properties": {
          "query": {
            "type": "string"
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/searchHit"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pass back as 'cursor' for the next page; null means this was the last."
          },
          "total": {
            "type": "integer",
            "description": "Total results across all pages."
          },
          "sandbox": {
            "type": "boolean",
            "description": "Present and true when the response is a sandbox fixture rather than a scored index search."
          }
        },
        "required": [
          "query",
          "results",
          "next_cursor",
          "total"
        ]
      },
      "desktop": {
        "type": "object",
        "properties": {
          "desktop": {
            "type": "string"
          },
          "command": {
            "type": "string",
            "description": "The one-line install command."
          },
          "pins": {
            "type": [
              "string",
              "null"
            ],
            "description": "The desktop this URL pre-answers, or null for /haus.sh, which asks."
          }
        },
        "required": [
          "desktop",
          "command",
          "pins"
        ]
      },
      "desktopPage": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/desktop"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          },
          "total": {
            "type": "integer"
          }
        },
        "required": [
          "results",
          "next_cursor",
          "total"
        ]
      },
      "app": {
        "type": "object",
        "properties": {
          "app": {
            "type": "string"
          },
          "repo": {
            "type": "string",
            "format": "uri-reference"
          },
          "release_metadata": {
            "type": "string",
            "format": "uri"
          },
          "latest_download": {
            "type": "string",
            "format": "uri"
          }
        },
        "required": [
          "app",
          "repo",
          "release_metadata",
          "latest_download"
        ]
      },
      "appPage": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/app"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          },
          "total": {
            "type": "integer"
          }
        },
        "required": [
          "results",
          "next_cursor",
          "total"
        ]
      },
      "batchRequest": {
        "type": "object",
        "properties": {
          "operations": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "items": {
              "type": "object",
              "oneOf": [
                {
                  "properties": {
                    "op": {
                      "type": "string",
                      "const": "search"
                    },
                    "query": {
                      "type": "string"
                    },
                    "limit": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 20,
                      "default": 5
                    }
                  },
                  "required": [
                    "op",
                    "query"
                  ]
                },
                {
                  "properties": {
                    "op": {
                      "type": "string",
                      "const": "release"
                    },
                    "app": {
                      "type": "string",
                      "enum": [
                        "pounce",
                        "perch"
                      ]
                    }
                  },
                  "required": [
                    "op",
                    "app"
                  ]
                },
                {
                  "properties": {
                    "op": {
                      "type": "string",
                      "const": "install"
                    },
                    "desktop": {
                      "type": "string",
                      "description": "Omit to list every desktop."
                    }
                  },
                  "required": [
                    "op"
                  ]
                }
              ]
            }
          },
          "sandbox": {
            "type": "boolean",
            "default": false,
            "description": "Run every operation against the sandbox fixtures: deterministic sample data, no live GitHub release lookups."
          }
        },
        "required": [
          "operations"
        ]
      },
      "batchResponse": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "op": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "ok": {
                  "type": "boolean"
                },
                "data": {
                  "description": "The operation's result when ok is true."
                },
                "error": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "integer"
                    },
                    "code": {
                      "type": "string"
                    }
                  },
                  "description": "Present when ok is false."
                }
              },
              "required": [
                "op",
                "ok"
              ]
            }
          },
          "sandbox": {
            "type": "boolean",
            "description": "Echoed when the batch ran in sandbox mode."
          }
        },
        "required": [
          "results"
        ]
      },
      "jobStatus": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "done",
              "failed"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Where to poll; present on the 202 body."
          },
          "result": {
            "$ref": "#/components/schemas/batchResponse",
            "description": "Present once status is done."
          },
          "error": {
            "type": "string",
            "description": "Present when status is failed."
          }
        },
        "required": [
          "id",
          "status",
          "created_at"
        ]
      },
      "askResponse": {
        "type": "object",
        "description": "The NLWeb envelope: _meta first, then the results.",
        "properties": {
          "_meta": {
            "type": "object",
            "properties": {
              "response_type": {
                "type": "string",
                "enum": [
                  "search_results",
                  "streaming"
                ]
              },
              "version": {
                "type": "string"
              },
              "service": {
                "type": "string"
              },
              "endpoint": {
                "type": "string"
              }
            },
            "required": [
              "response_type",
              "version",
              "service",
              "endpoint"
            ]
          },
          "query": {
            "type": "string"
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/searchHit"
            }
          }
        },
        "required": [
          "_meta",
          "query",
          "results"
        ]
      },
      "serverCard": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "serverUrl": {
            "type": "string",
            "format": "uri"
          },
          "tools": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "docsServerUrl": {
            "type": "string",
            "format": "uri",
            "description": "The docs-only MCP transport."
          }
        },
        "required": [
          "name",
          "description",
          "version",
          "serverUrl",
          "tools"
        ]
      },
      "jsonRpcRequest": {
        "type": "object",
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "description": "Absent on notifications.",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "integer"
              }
            ]
          },
          "method": {
            "type": "string"
          },
          "params": {
            "type": "object"
          }
        },
        "required": [
          "jsonrpc",
          "method"
        ]
      },
      "jsonRpcError": {
        "type": "object",
        "description": "A JSON-RPC 2.0 error object: a numeric code beside a human-readable message, so a client can branch without parsing prose.",
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "description": "The request id, or null when the id was unreadable."
          },
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "integer",
                "description": "JSON-RPC error code (-32700 parse, -32600 invalid request, -32601 method not found, -32602 invalid params)."
              },
              "message": {
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        },
        "required": [
          "jsonrpc",
          "id",
          "error"
        ]
      },
      "jsonRpcResponse": {
        "description": "A JSON-RPC 2.0 result (single object, or an array of objects for a batch request). Tool failures ride inside result.isError with a structuredContent error payload; transport-level failures use the jsonRpcError shape.",
        "oneOf": [
          {
            "$ref": "#/components/schemas/jsonRpcResult"
          },
          {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/jsonRpcResult"
            }
          }
        ]
      },
      "jsonRpcResult": {
        "type": "object",
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "description": "The request id this result answers."
          },
          "result": {
            "description": "The method's result. For tools/call: content blocks, optional structuredContent, and isError true on a tool-level failure."
          }
        },
        "required": [
          "jsonrpc",
          "id",
          "result"
        ]
      },
      "protectedResource": {
        "type": "object",
        "description": "RFC 9728 Protected Resource Metadata for the public resource this host serves.",
        "properties": {
          "resource": {
            "type": "string",
            "format": "uri"
          },
          "resource_documentation": {
            "type": "string",
            "format": "uri",
            "description": "Points at /auth.md."
          },
          "authorization_servers": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            },
            "description": "Empty: no authorization server stands behind this resource."
          },
          "scopes_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "bearer_methods_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "response_types_supported": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "resource",
          "authorization_servers",
          "scopes_supported",
          "bearer_methods_supported"
        ]
      },
      "signatureDirectory": {
        "type": "object",
        "properties": {
          "keys": {
            "type": "array",
            "description": "Empty: this host signs no responses.",
            "items": {
              "type": "object",
              "description": "An Ed25519 JWK (kty OKP, crv Ed25519, kid, nbf, exp).",
              "properties": {
                "kty": {
                  "type": "string"
                },
                "crv": {
                  "type": "string"
                },
                "kid": {
                  "type": "string"
                },
                "nbf": {
                  "type": "integer"
                },
                "exp": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "required": [
          "keys"
        ]
      },
      "mcpManifest": {
        "type": "object",
        "properties": {
          "$schema": {
            "type": "string",
            "format": "uri"
          },
          "mcpServers": {
            "type": "object",
            "description": "One entry per MCP transport: hausfold (install commands, releases, docs search) and hausfold-docs (docs search alone).",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string",
                  "const": "streamable-http"
                },
                "url": {
                  "type": "string",
                  "format": "uri"
                },
                "description": {
                  "type": "string"
                }
              },
              "required": [
                "type",
                "url"
              ]
            }
          }
        },
        "required": [
          "mcpServers"
        ]
      }
    }
  },
  "externalDocs": {
    "description": "The same surface written for a human, with quickstarts.",
    "url": "https://hausfold.co/developers/"
  }
}
