{
  "openapi": "3.1.0",
  "info": {
    "title": "A2UI Atomic Catalog",
    "version": "1.0.0",
    "summary": "Typed UI vocabulary and rendering API for AI agents",
    "description": "474 typed UI atoms an agent composes into real rendered interfaces — charts, gauges, steppers, dashboards — across 8 surfaces including web, Google Meet, Apps Script, Google Chat and MCP Apps hosts. The agent names a component from a fixed vocabulary; a renderer that already knows that component draws it, so no HTML is ever generated by the model.\n\nThe primary integration surface is the MCP (Model Context Protocol) server at /mcp, which exposes its tools over JSON-RPC 2.0. The REST paths documented here are the supporting surfaces: the machine-readable catalog, the declared data proxy, and the natural language composer.\n\nNo API key, no signup, no OAuth required for normal use.",
    "license": {
      "name": "MIT",
      "identifier": "MIT"
    },
    "contact": {
      "name": "A2UI Atomic Catalog",
      "url": "https://github.com/a2uicatalog/a2ui"
    }
  },
  "servers": [
    {
      "url": "https://a2uicatalog.ai",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Agent-facing overview (llms.txt)",
    "url": "https://a2uicatalog.ai/llms.txt"
  },
  "tags": [
    {
      "name": "mcp",
      "description": "Model Context Protocol server — the primary integration surface"
    },
    {
      "name": "catalog",
      "description": "Machine-readable atom vocabulary and discovery documents"
    },
    {
      "name": "compose",
      "description": "Natural language to atom blocks"
    },
    {
      "name": "data",
      "description": "Declared, rate-limited data sources for live atoms"
    }
  ],
  "paths": {
    "/mcp": {
      "get": {
        "tags": [
          "mcp"
        ],
        "operationId": "describeMcpServer",
        "summary": "Server descriptor",
        "description": "Content-negotiated. Accept: application/json returns a machine-readable descriptor (name, transport, tools, rate limits, docs). Accept: text/html returns a human landing page. Accept: text/event-stream returns 405 — this server is stateless and has no server-initiated stream.",
        "parameters": [
          {
            "name": "Accept",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "application/json",
                "text/html",
                "text/plain"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Server descriptor",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServerDescriptor"
                }
              }
            }
          },
          "405": {
            "description": "Accept: text/event-stream — no server-initiated stream"
          }
        }
      },
      "post": {
        "tags": [
          "mcp"
        ],
        "operationId": "mcpJsonRpc",
        "summary": "MCP JSON-RPC 2.0 endpoint",
        "description": "Streamable HTTP transport. Supports initialize, tools/list, tools/call, ping and notifications. Call tools/list for the authoritative tool set with full input schemas.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonRpcRequest"
              },
              "examples": {
                "toolsList": {
                  "summary": "List tools",
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 1,
                    "method": "tools/list"
                  }
                },
                "callTool": {
                  "summary": "Render a payload to a shareable URL",
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 2,
                    "method": "tools/call",
                    "params": {
                      "name": "preview_url",
                      "arguments": {
                        "payload": {
                          "title": "Demo",
                          "blocks": [
                            {
                              "type": "stat_card",
                              "value": "1,234",
                              "label": "Daily users",
                              "delta": "+12%"
                            }
                          ]
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcResponse"
                }
              }
            }
          }
        }
      }
    },
    "/spec.json": {
      "get": {
        "tags": [
          "catalog"
        ],
        "operationId": "getCatalogSpec",
        "summary": "Full atom vocabulary",
        "description": "Every published atom with its type, description, field contracts and per-surface support. This is what an agent reads to compose a payload without guessing field names.",
        "responses": {
          "200": {
            "description": "Catalog specification",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CatalogSpec"
                }
              }
            }
          }
        }
      }
    },
    "/catalogue/index.json": {
      "get": {
        "tags": [
          "catalog"
        ],
        "operationId": "getCatalogIndex",
        "summary": "Catalog selection menu",
        "description": "Compact index of catalogs with a when-to-use line each, so an agent can select a slice without loading the full vocabulary into context.",
        "responses": {
          "200": {
            "description": "Catalog index",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/catalogue/atoms-json-schema.json": {
      "get": {
        "tags": [
          "catalog"
        ],
        "operationId": "getAtomJsonSchema",
        "summary": "Strict per-atom JSON Schema",
        "description": "Per-atom JSON Schema suitable for constrained decoding (response_format / grammar), so a model cannot emit an atom that fails validation.",
        "responses": {
          "200": {
            "description": "JSON Schema",
            "content": {
              "application/schema+json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/ai-catalog.json": {
      "get": {
        "tags": [
          "catalog"
        ],
        "operationId": "getArdCatalog",
        "summary": "Agentic Resource Discovery catalog",
        "description": "ARD-compliant discovery document (specVersion 1.0) describing this host and every atom as an addressable capability.",
        "responses": {
          "200": {
            "description": "ARD catalog",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/compose": {
      "post": {
        "tags": [
          "compose"
        ],
        "operationId": "composeFromText",
        "summary": "Natural language to atom blocks",
        "description": "Two-stage routing over atom embeddings returns candidate atom blocks for a plain-English UI request. Rate limited to 20 requests per IP per day (free-tier inference).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "prompt"
                ],
                "properties": {
                  "prompt": {
                    "type": "string",
                    "description": "Plain-English description of the UI wanted"
                  }
                }
              },
              "example": {
                "prompt": "a dashboard with daily users and a bar chart of traffic sources"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Atom blocks",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "blocks": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AtomBlock"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/data/{source}": {
      "get": {
        "tags": [
          "data"
        ],
        "operationId": "getDeclaredDataSource",
        "summary": "Declared data-source proxy",
        "description": "Proxies a data source declared in atoms/data-sources.yaml, with per-source cache, rate and egress budgets. Only declared sources are reachable — this is the single place network access is granted to live atoms.",
        "parameters": [
          {
            "name": "source",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Declared source id"
          }
        ],
        "responses": {
          "200": {
            "description": "Source payload",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/ask": {
      "get": {
        "tags": [
          "catalog"
        ],
        "operationId": "askCatalogGet",
        "summary": "NLWeb natural-language catalog search (GET)",
        "description": "NLWeb protocol (nlweb.ai) endpoint over the atom catalog. Ask a plain-English question — \"what atom do I use for a countdown timer?\" — and get back ranked, typed catalog matches instead of having to scrape HTML. Reuses the same embedding search /api/compose uses to pick relevant atoms.",
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "mode",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated: list (default), summarize, generate."
          },
          {
            "name": "site",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "query_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Accept",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "text/event-stream requests an SSE stream (start/result/complete events)."
          }
        ],
        "responses": {
          "200": {
            "description": "Search results",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NLWebAskResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "catalog"
        ],
        "operationId": "askCatalogPost",
        "summary": "NLWeb natural-language catalog search (POST)",
        "description": "Same as GET /ask; accepts the flat REST body or the fuller NLWeb protocol's nested query/prefer/meta shape.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NLWebAskRequest"
              },
              "example": {
                "query": "what atom do I use for a countdown timer?",
                "mode": "list,summarize"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Search results",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NLWebAskResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/s/{id}": {
      "get": {
        "tags": [
          "mcp"
        ],
        "operationId": "getPublishedSurface",
        "summary": "Published short link",
        "description": "Resolves a short link minted by the publish_url tool. Appending .json returns the stored payload by reference instead of redirecting to the rendered surface.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to the rendered surface"
          },
          "200": {
            "description": "Stored payload (when requested as .json)",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/A2uiPayload"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    }
  },
  "components": {
    "headers": {
      "ApiVersion": {
        "description": "Wire-format version of this response, currently \"1\". The additive alternative to a /v1/ path prefix: bump only on a documented, intentional change to a response shape.",
        "schema": {
          "type": "string",
          "example": "1"
        }
      }
    },
    "responses": {
      "RateLimited": {
        "description": "Rate limit reached. See the descriptor at GET /mcp (Accept: application/json) for current limits.",
        "headers": {
          "X-RateLimit-Limit": {
            "description": "Requests allowed in the current window.",
            "schema": {
              "type": "integer"
            }
          },
          "X-RateLimit-Remaining": {
            "description": "Requests remaining in the current window.",
            "schema": {
              "type": "integer"
            }
          },
          "Retry-After": {
            "description": "Seconds until the window resets (present on some sources).",
            "schema": {
              "type": "integer"
            }
          },
          "X-API-Version": {
            "$ref": "#/components/headers/ApiVersion"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "NotFound": {
        "description": "No resource at this path.",
        "headers": {
          "X-API-Version": {
            "$ref": "#/components/headers/ApiVersion"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      }
    },
    "schemas": {
      "ErrorResponse": {
        "type": "object",
        "description": "Shape of every non-2xx JSON response on the REST surface (the /mcp JSON-RPC endpoint uses JsonRpcResponse.error instead — see that schema — and a tools/call failure uses result.isError plus result._meta.errorCode, a stable machine-readable string such as UNKNOWN_TOOL or INVALID_PAYLOAD_SHAPE).",
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string",
            "description": "Human-readable error message."
          },
          "note": {
            "type": "string",
            "description": "Optional extra guidance (e.g. how to raise a limit)."
          }
        }
      },
      "ServerDescriptor": {
        "type": "object",
        "description": "Machine-readable description of the MCP server.",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "protocol": {
            "type": "string",
            "const": "mcp"
          },
          "transport": {
            "type": "string",
            "const": "streamable-http"
          },
          "endpoint": {
            "type": "string",
            "format": "uri"
          },
          "authentication": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string"
              },
              "required": {
                "type": "boolean"
              }
            }
          },
          "tools": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                }
              }
            }
          },
          "documentation": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "format": "uri"
            }
          },
          "rateLimits": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "JsonRpcRequest": {
        "type": "object",
        "required": [
          "jsonrpc",
          "method"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "oneOf": [
              {
                "type": "integer"
              },
              {
                "type": "string"
              }
            ]
          },
          "method": {
            "type": "string",
            "examples": [
              "initialize",
              "tools/list",
              "tools/call",
              "ping"
            ]
          },
          "params": {
            "type": "object"
          }
        }
      },
      "JsonRpcResponse": {
        "type": "object",
        "required": [
          "jsonrpc"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "oneOf": [
              {
                "type": "integer"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "result": {
            "type": "object",
            "description": "For tools/call, a failed tool run sets result.isError: true — see result._meta.errorCode below — rather than the top-level error field, which is reserved for JSON-RPC protocol errors (bad method, parse error)."
          },
          "error": {
            "type": "object",
            "description": "Protocol-level error (JSON-RPC 2.0 §5.1).",
            "properties": {
              "code": {
                "type": "integer",
                "examples": [
                  -32700,
                  -32600,
                  -32601,
                  -32603
                ]
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "AtomBlock": {
        "type": "object",
        "description": "One typed UI component. `type` selects the atom; the remaining fields are that atom's declared contract (see /spec.json).",
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "type": "string",
            "examples": [
              "stat_card",
              "chartjs_bar",
              "stepper",
              "callout"
            ]
          }
        },
        "additionalProperties": true
      },
      "A2uiPayload": {
        "type": "object",
        "description": "A renderable surface: a title, a theme, and an ordered list of atom blocks.",
        "required": [
          "blocks"
        ],
        "properties": {
          "title": {
            "type": "string"
          },
          "theme": {
            "type": "string",
            "enum": [
              "light",
              "dark",
              "site"
            ]
          },
          "blocks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AtomBlock"
            }
          }
        }
      },
      "CatalogSpec": {
        "type": "object",
        "properties": {
          "catalogId": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "atomCount": {
            "type": "integer"
          },
          "atoms": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        }
      },
      "NLWebAskRequest": {
        "type": "object",
        "description": "Accepts either the flat REST fields below, OR query as a nested {\"text\": \"...\"} object per the fuller NLWeb protocol spec.",
        "required": [
          "query"
        ],
        "properties": {
          "query": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string"
                  }
                }
              }
            ]
          },
          "site": {
            "type": "string"
          },
          "mode": {
            "type": "string",
            "description": "Comma-separated: list, summarize, generate."
          },
          "streaming": {
            "type": "boolean"
          },
          "query_id": {
            "type": "string"
          }
        }
      },
      "NLWebAskResult": {
        "type": "object",
        "description": "One catalog match, schema.org-flavored per the NLWeb spec.",
        "properties": {
          "@type": {
            "type": "string",
            "examples": [
              "Thing",
              "SearchSummary"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "name": {
            "type": "string"
          },
          "site": {
            "type": "string"
          },
          "score": {
            "type": "number"
          },
          "description": {
            "type": "string"
          },
          "schema_object": {
            "type": "object",
            "description": "The full spec.json atom entry."
          }
        }
      },
      "NLWebAskResponse": {
        "type": "object",
        "properties": {
          "query_id": {
            "type": "string"
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/NLWebAskResult"
            }
          },
          "generated": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AtomBlock"
            },
            "description": "Present only for mode=generate: composed atom blocks answering the query."
          },
          "routing": {
            "type": "string",
            "examples": [
              "embed",
              "chat"
            ]
          },
          "_meta": {
            "type": "object",
            "properties": {
              "response_type": {
                "type": "string",
                "const": "answer"
              },
              "version": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  }
}
