{
  "openapi": "3.1.0",
  "info": {
    "title": "API pública de Alexandre Caramaschi",
    "version": "1.11.0",
    "description": "Consulta pública de negócio, contato, cursos, texto e mídia das aulas, busca híbrida nas aulas, grafo de conhecimento dos cursos e pesquisa. MCP JSON-RPC e REST legado compartilham /api/mcp. Não envia mensagens, não realiza compras e não expõe administração. O registro de agente (RFC 7591) e o token client_credentials são opcionais: identificam o cliente nas respostas e ampliam o corpo aceito; sem token, tudo continua público."
  },
  "servers": [
    {
      "url": "https://alexandrecaramaschi.com"
    }
  ],
  "security": [],
  "externalDocs": {
    "url": "https://alexandrecaramaschi.com/developers.md"
  },
  "x-markdown-negotiation": {
    "summary": "Qualquer rota pública em HTML responde em Markdown por negociação de conteúdo na própria URL, sem caminho novo.",
    "request": {
      "header": "Accept",
      "value": "text/markdown",
      "rule": "Markdown somente quando q(text/markdown) > 0 e q(text/markdown) > q(text/html), conforme RFC 9110. Curingas */* e text/* não contam. Alias legado: application/llm."
    },
    "response": {
      "contentType": "text/markdown; charset=utf-8",
      "headers": [
        "Vary: Accept",
        "Link: <url canônica>; rel=\"canonical\"",
        "x-markdown-tokens: estimativa (caracteres / 4), método em x-markdown-tokens-method",
        "Cache-Control: public, s-maxage=3600, stale-while-revalidate=86400"
      ],
      "body": "Front matter YAML (title, canonical, author, datas, description, language), H1 e seções."
    },
    "routes": [
      "/",
      "/artigos/{slug}",
      "/educacao",
      "/educacao/{curso}",
      "/educacao/{curso}/{capitulo}",
      "/insights/{slug}",
      "/publicacoes",
      "/glossario",
      "/faq",
      "/cases",
      "páginas-pilar da raiz"
    ],
    "excluded": [
      "/api/*",
      "/admin*",
      "/auth/*",
      "/_next/*",
      "arquivos com extensão",
      "/mcp",
      "/.well-known/*",
      "páginas privadas ou noindex",
      "/artefacto/*",
      "/guia/*",
      "/faq/{sub}",
      "/caso/*",
      "/landing/*"
    ],
    "example": "curl -H \"Accept: text/markdown\" https://alexandrecaramaschi.com/artigos/geo-vs-seo-vs-aeo-o-que-muda-na-pratica",
    "docs": "https://alexandrecaramaschi.com/developers.md"
  },
  "x-agent-auth": {
    "summary": "Registro dinâmico anônimo e token client_credentials de uma hora, opcionais para o MCP.",
    "authorization_server_metadata": "https://alexandrecaramaschi.com/.well-known/oauth-authorization-server",
    "protected_resource_metadata": "https://alexandrecaramaschi.com/.well-known/oauth-protected-resource/api/mcp",
    "jwks_uri": "https://alexandrecaramaschi.com/.well-known/jwks.json",
    "docs": "https://alexandrecaramaschi.com/auth.md"
  },
  "paths": {
    "/api/mcp": {
      "get": {
        "operationId": "discoverPublicTools",
        "summary": "Consultar metadados e ferramentas disponíveis",
        "security": [
          {},
          {
            "agentClientCredentials": [
              "mcp:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Metadados públicos",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Token Bearer inválido ou expirado; WWW-Authenticate aponta para /.well-known/oauth-protected-resource."
          },
          "403": {
            "description": "Origin não permitida."
          },
          "405": {
            "description": "GET com Accept text/event-stream: o servidor não abre canal SSE."
          }
        }
      },
      "post": {
        "operationId": "callPublicTool",
        "summary": "Executar chamada MCP ou consulta REST legada",
        "description": "MCP exige Accept: application/json, text/event-stream. Após initialize, envie MCP-Protocol-Version negociado. Limite de corpo: 16 KiB sem token; 64 KiB com token de agente válido (Authorization: Bearer). Com token, initialize e tools/call trazem _meta.client_id e a resposta traz X-Agent-Client. Desde a versão 1.8.0, em tools/call, limit e maxChars aceitam também número inteiro, e erro de entrada (argumento desconhecido, tipo ou faixa inválidos) volta como resultado com isError: true e structuredContent {error: 'invalid_argument', message, argument, accepted}; o erro JSON-RPC -32602 fica para ferramenta desconhecida, arguments que não é objeto e corpo malformado. No REST legado, os argumentos vão no próprio corpo, ao lado de tool, sempre como texto.",
        "security": [
          {},
          {
            "agentClientCredentials": [
              "mcp:read"
            ]
          }
        ],
        "parameters": [
          {
            "in": "header",
            "name": "MCP-Protocol-Version",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "2026-07-28",
                "2025-11-25",
                "2025-06-18"
              ]
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/RpcRequest"
                  },
                  {
                    "$ref": "#/components/schemas/LegacyRequest"
                  }
                ]
              },
              "examples": {
                "mcp": {
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 1,
                    "method": "tools/list"
                  }
                },
                "legacy": {
                  "value": {
                    "tool": "listCourses"
                  }
                },
                "legacySearch": {
                  "value": {
                    "tool": "searchContent",
                    "query": "como medir citações em IA",
                    "limit": "5"
                  }
                },
                "legacyGraph": {
                  "value": {
                    "tool": "exploreKnowledgeGraph",
                    "node": "x:geo",
                    "limit": "10"
                  }
                },
                "mcpGraph": {
                  "value": {
                    "jsonrpc": "2.0",
                    "id": 2,
                    "method": "tools/call",
                    "params": {
                      "name": "exploreKnowledgeGraph",
                      "arguments": {
                        "query": "dados estruturados",
                        "limit": 5
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resultado público ou erro JSON-RPC",
            "headers": {
              "X-Agent-Client": {
                "description": "client_id do agente, presente somente com token válido.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "202": {
            "description": "Notificação MCP aceita, sem corpo."
          },
          "400": {
            "description": "JSON, protocolo ou mensagem inválidos."
          },
          "401": {
            "description": "Token Bearer inválido ou expirado; WWW-Authenticate aponta para /.well-known/oauth-protected-resource."
          },
          "403": {
            "description": "Origin não permitida."
          },
          "404": {
            "description": "Ferramenta ou curso REST inexistente."
          },
          "406": {
            "description": "Accept incompatível com MCP."
          },
          "413": {
            "description": "Corpo acima de 16 KiB (64 KiB com token)."
          },
          "415": {
            "description": "Content-Type incompatível."
          },
          "429": {
            "description": "Limite de requisições excedido; respeite Retry-After."
          },
          "503": {
            "description": "Token enviado, mas a chave de verificação não está configurada no ambiente."
          }
        }
      }
    },
    "/api/mcp/markdown": {
      "get": {
        "operationId": "readMarkdown",
        "summary": "Consultar a apresentação pública em Markdown",
        "description": "Espelho editorial da home em Markdown, com last_updated e contagens geradas no build. É a mesma resposta que GET / devolve com Accept: text/markdown; as demais rotas públicas negociam Markdown na própria URL (ver x-markdown-negotiation).",
        "responses": {
          "200": {
            "description": "Documento Markdown",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/feed.json": {
      "get": {
        "operationId": "readPublicationsFeed",
        "summary": "Consultar publicações recentes em JSON Feed",
        "responses": {
          "200": {
            "description": "JSON Feed",
            "content": {
              "application/feed+json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/oauth/register": {
      "post": {
        "operationId": "registerAgentClient",
        "summary": "Registrar um cliente de agente (RFC 7591, identidade anônima)",
        "description": "Sem cadastro humano, sem e-mail e sem persistência: client_id e client_secret são verificáveis por HMAC derivado do segredo do servidor. Limite de 10 registros por IP por hora. Documentação em https://alexandrecaramaschi.com/auth.md.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientRegistrationRequest"
              },
              "examples": {
                "minimo": {
                  "value": {
                    "client_name": "meu-agente"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Cliente registrado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientRegistrationResponse"
                }
              }
            }
          },
          "400": {
            "description": "invalid_client_metadata (client_name ausente ou metadados fora do aceito) ou invalid_request (JSON inválido).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "413": {
            "description": "Corpo acima de 8 KiB."
          },
          "415": {
            "description": "Content-Type deve ser application/json."
          },
          "429": {
            "description": "Mais de 10 registros por IP na última hora; respeite Retry-After."
          },
          "503": {
            "description": "Chave de assinatura não configurada no ambiente."
          }
        }
      }
    },
    "/api/oauth/token": {
      "post": {
        "operationId": "issueAgentToken",
        "summary": "Emitir token de acesso (RFC 6749, seção 4.4, client_credentials)",
        "description": "Credenciais por client_secret_post (corpo) ou client_secret_basic (Authorization: Basic). Devolve um JWT ES256 de uma hora com iss https://alexandrecaramaschi.com, sub igual ao client_id, aud https://alexandrecaramaschi.com/api/mcp e scope mcp:read. Chave pública em /.well-known/jwks.json.",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/TokenRequest"
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TokenRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token emitido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request, unsupported_grant_type ou invalid_scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "401": {
            "description": "invalid_client: client_id ou client_secret inválidos.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "413": {
            "description": "Corpo acima de 8 KiB."
          },
          "415": {
            "description": "Content-Type deve ser application/x-www-form-urlencoded ou application/json."
          },
          "429": {
            "description": "Limite de requisições excedido; respeite Retry-After."
          },
          "503": {
            "description": "Chave de assinatura não configurada no ambiente."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "agentClientCredentials": {
        "type": "oauth2",
        "description": "Opcional. Registre um cliente em /api/oauth/register e troque as credenciais por um token de uma hora em /api/oauth/token. Metadados em /.well-known/oauth-authorization-server.",
        "flows": {
          "clientCredentials": {
            "tokenUrl": "https://alexandrecaramaschi.com/api/oauth/token",
            "scopes": {
              "mcp:read": "Consulta das ferramentas públicas com identificação do cliente nas respostas."
            }
          }
        }
      }
    },
    "schemas": {
      "RpcRequest": {
        "type": "object",
        "required": [
          "jsonrpc",
          "method"
        ],
        "properties": {
          "jsonrpc": {
            "const": "2.0"
          },
          "id": {
            "type": [
              "string",
              "integer"
            ]
          },
          "method": {
            "enum": [
              "initialize",
              "notifications/initialized",
              "notifications/cancelled",
              "ping",
              "tools/list",
              "tools/call"
            ]
          },
          "params": {
            "type": "object"
          }
        }
      },
      "LegacyRequest": {
        "type": "object",
        "required": [
          "tool"
        ],
        "properties": {
          "tool": {
            "enum": [
              "getBusinessInfo",
              "getLocation",
              "contactUs",
              "listCourses",
              "getCourseDetails",
              "getCatalogManifest",
              "getCourseModules",
              "getModuleContent",
              "searchContent",
              "getModuleMedia",
              "exploreKnowledgeGraph",
              "planLearningPath",
              "getResearchPublications",
              "search",
              "fetch"
            ]
          },
          "slug": {
            "type": "string",
            "description": "Obrigatório para getCourseDetails, getCourseModules, getModuleContent e getModuleMedia; opcional em searchContent para buscar em um curso só."
          },
          "module": {
            "type": "string",
            "description": "Id ou slug ASCII do módulo (getCourseModules). Obrigatório em getModuleContent; opcional em getModuleMedia."
          },
          "cursor": {
            "type": "string",
            "description": "nextCursor de uma resposta parcial anterior (getModuleContent e getModuleMedia) ou, desde a 1.9.0, da página anterior de searchContent com a mesma consulta e os mesmos filtros."
          },
          "maxChars": {
            "type": "string",
            "description": "Orçamento por resposta em caracteres, inteiro de 2000 a 40000 escrito como texto (getModuleContent e getModuleMedia)."
          },
          "query": {
            "type": "string",
            "description": "Pergunta ou termos em português, até 200 caracteres. Obrigatório em searchContent e em search; em exploreKnowledgeGraph, um dos modos."
          },
          "limit": {
            "type": "string",
            "description": "Máximo de resultados, inteiro de 1 a 20 escrito como texto (searchContent e exploreKnowledgeGraph)."
          },
          "transcripts": {
            "type": "string",
            "enum": [
              "include",
              "exclude",
              "only"
            ],
            "description": "searchContent: include (padrão) também busca a fala das vídeo-aulas e dos podcasts; exclude busca só o texto; only, só a fala."
          },
          "ranking": {
            "type": "string",
            "enum": [
              "hybrid",
              "lexical"
            ],
            "description": "searchContent: hybrid (padrão) junta BM25 e semelhança de sentido; lexical usa só BM25."
          },
          "node": {
            "type": "string",
            "description": "exploreKnowledgeGraph: id de um nó (c:<curso>, k:<curso>#<capítulo> ou x:<conceito>)."
          },
          "from": {
            "type": "string",
            "description": "exploreKnowledgeGraph: id do nó de partida de um caminho; exige to."
          },
          "to": {
            "type": "string",
            "description": "exploreKnowledgeGraph: id do nó de chegada de um caminho; exige from."
          },
          "id": {
            "type": "string",
            "description": "fetch (desde a 1.10.0): id devolvido por search (<curso>/<módulo>) ou id do grafo (c:, k:, x:), até 300 caracteres."
          }
        },
        "additionalProperties": false
      },
      "ClientRegistrationRequest": {
        "type": "object",
        "required": [
          "client_name"
        ],
        "properties": {
          "client_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "client_uri": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048
          },
          "contacts": {
            "type": "array",
            "maxItems": 5,
            "items": {
              "type": "string",
              "format": "email"
            }
          },
          "software_id": {
            "type": "string",
            "maxLength": 120
          },
          "software_version": {
            "type": "string",
            "maxLength": 40
          },
          "grant_types": {
            "type": "array",
            "items": {
              "const": "client_credentials"
            },
            "description": "Opcional; somente client_credentials."
          },
          "token_endpoint_auth_method": {
            "enum": [
              "client_secret_post",
              "client_secret_basic"
            ]
          },
          "scope": {
            "const": "mcp:read"
          }
        }
      },
      "ClientRegistrationResponse": {
        "type": "object",
        "required": [
          "client_id",
          "client_secret",
          "client_id_issued_at",
          "client_secret_expires_at",
          "client_name",
          "grant_types",
          "token_endpoint_auth_method",
          "scope"
        ],
        "properties": {
          "client_id": {
            "type": "string",
            "pattern": "^agt_[A-Za-z0-9_-]{32}$"
          },
          "client_secret": {
            "type": "string",
            "description": "base64url de HMAC-SHA256 sobre o client_id; guarde, não é reenviado."
          },
          "client_id_issued_at": {
            "type": "integer",
            "description": "Segundos desde 1970-01-01T00:00:00Z."
          },
          "client_secret_expires_at": {
            "const": 0,
            "description": "0: o segredo não expira por tempo."
          },
          "client_name": {
            "type": "string"
          },
          "client_uri": {
            "type": "string"
          },
          "contacts": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "software_id": {
            "type": "string"
          },
          "software_version": {
            "type": "string"
          },
          "grant_types": {
            "const": [
              "client_credentials"
            ]
          },
          "token_endpoint_auth_method": {
            "const": "client_secret_post"
          },
          "scope": {
            "const": "mcp:read"
          }
        }
      },
      "TokenRequest": {
        "type": "object",
        "required": [
          "grant_type"
        ],
        "properties": {
          "grant_type": {
            "const": "client_credentials"
          },
          "client_id": {
            "type": "string",
            "description": "Obrigatório quando não vier por Authorization: Basic."
          },
          "client_secret": {
            "type": "string",
            "description": "Obrigatório quando não vier por Authorization: Basic."
          },
          "scope": {
            "const": "mcp:read"
          }
        }
      },
      "TokenResponse": {
        "type": "object",
        "required": [
          "access_token",
          "token_type",
          "expires_in",
          "scope"
        ],
        "properties": {
          "access_token": {
            "type": "string",
            "description": "JWT ES256; verifique com /.well-known/jwks.json."
          },
          "token_type": {
            "const": "Bearer"
          },
          "expires_in": {
            "const": 3600
          },
          "scope": {
            "const": "mcp:read"
          }
        }
      },
      "OAuthError": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "error_description": {
            "type": "string"
          }
        }
      }
    }
  }
}
