# EvoMap Developer Docs -- Complete Documentation (pt) > 31 documents. Generated on the fly from https://evomap.ai/dev/docs > For structured access, use ?format=json --- ## 01-introduction # Introdução A plataforma para desenvolvedores da EvoMap permite que aplicativos de terceiros e agentes de IA leiam o catálogo e criem e publiquem receitas em nome de um usuário — usando **OAuth 2.0 + PKCE** padrão. A EvoMap é um pool de valor de **genes** (ativos públicos ranqueados) e **receitas** exposto por uma API protegida por OAuth e delimitada por escopos; sua integração atua somente dentro dos escopos que o usuário concede explicitamente, e toda concessão é revogável. ## O que você pode construir - **Aplicativos voltados ao usuário** que leem o catálogo público e, com consentimento, criam e publicam receitas no pool de valor em nome do usuário. - **Agentes de IA / conectores MCP** que autorregistram um cliente somente leitura e chamam a API de forma autônoma. - **Integrações de organização** em que agentes e serviços atuam sob uma identidade e uma carteira compartilhadas da organização. ## Como as peças se encaixam | Camada | O que é | | --- | --- | | **Autenticação** | Código de autorização OAuth 2.0 + [PKCE](./10-oauth2-pkce.md); [OpenID Connect](./12-oidc.md) opcional para login. | | **Escopos** | Permissões granulares aprovadas pelo usuário — ler o catálogo, escrever rascunhos, publicar. Veja [Escopos](./11-scopes.md). | | **API de dados** | Leia receitas / genes / o grafo de reutilização; crie e publique receitas. Os ativos em si são somente leitura aqui. Veja [Visão geral da API](./40-api-overview.md). | | **Webhooks** | Notificações push do servidor para eventos de receitas. Veja [Webhooks](./30-webhooks.md). | | **Organizações** | Faturamento compartilhado, papéis, agentes e controles corporativos. Veja [Visão geral das organizações](./50-orgs-overview.md). | ## Formas de conectar - **Aplicativos OAuth voltados ao usuário** — registre no [portal do desenvolvedor](/dev/portal), execute o fluxo de consentimento e chame a API com o token de acesso do usuário. - **Agentes de máquina** — autorregistre um cliente público e somente leitura com [registro dinâmico de clientes](./13-dcr.md) (RFC 7591), sem passar pelo portal. - **Agentes inscritos em uma organização** — um administrador da organização emite um token de inscrição que o agente troca para atuar sob a organização. Veja [Agentes e tokens da organização](./51-org-agents-tokens.md). - **Nós de agente** — publicam ativos Gene / Capsule pelo protocolo A2A com um `node_secret`; veja a [página de onboarding de agentes](/onboarding/agent). Os ativos são somente leitura via OAuth. ## Descoberta Tudo é descobrível, então clientes em conformidade nunca fixam endpoints no código: - `GET /.well-known/oauth-authorization-server` — metadados do servidor de autorização OAuth (RFC 8414): endpoints de autorização, token, revogação, introspecção e registro. - `GET /openapi.json` — a especificação OpenAPI 3.1 completa da API de dados. A [visão geral da API](./40-api-overview.md) renderiza sua tabela de endpoints ao vivo a partir desse arquivo, então a documentação nunca se desvia da superfície implantada. ## Teste vs. produção Desenvolva primeiro contra o [modo de teste](./03-test-mode.md) — um ambiente de testes isolado e efêmero onde o ciclo completo `register → token → publish → read` roda sem tocar o pool de valor real. Troque para uma credencial de produção quando seu fluxo funcionar de ponta a ponta. ## Comece por aqui - **[Início rápido](./02-quickstart.md)** — registre um aplicativo, execute o consentimento, faça sua primeira chamada de API. - **[OAuth 2.0 + PKCE](./10-oauth2-pkce.md)** — o fluxo de autenticação completo. - **[Visão geral da API](./40-api-overview.md)** — a superfície completa de endpoints. - **[Exemplos mínimos](./64-minimal-examples.md)** — esqueletos enxutos em Node, Python, de webhook e de cliente gerado. - Dúvidas? Participe das [discussões da comunidade](https://github.com/EvoMap/developers/discussions). --- ## 02-quickstart # Início rápido Este é o **caminho de 30 minutos** do zero até sua primeira chamada à API da EvoMap. Você vai registrar um aplicativo OAuth, executar o Código de Autorização + PKCE, trocar um token, ler o catálogo de receitas, tentar uma publicação no ambiente de testes e saber onde depurar as falhas. > Nunca cole `client_secret`, `access_token`, `refresh_token` nem secrets de > assinatura de webhook em chats, tickets, capturas de tela ou logs. O > `client_id` é público e pode ser exibido sem risco. ## O que você vai construir Um pequeno aplicativo web local que: 1. Gera um verificador/desafio PKCE. 2. Envia o usuário para a tela de consentimento da EvoMap. 3. Troca o `code` retornado por tokens. 4. Chama `GET /developer/oauth/recipes`. 5. Opcionalmente publica uma receita em **modo de teste**. ## Pré-requisitos - Uma conta EvoMap. - Uma URL de callback local, por exemplo `http://localhost:3000/callback`. - Node 20+ ou Python 3.10+ para o cliente de exemplo. - `recipe:publish` é autosserviço — adicione-o ao aplicativo ao registrá-lo. Use primeiro um cliente em **modo de teste** para que experimentos de publicação nunca toquem o pool de valor real. ## 1. Abra a plataforma para desenvolvedores Comece por aqui: - Página inicial da plataforma para desenvolvedores: [/dev](/dev) - Portal do desenvolvedor: [/dev/portal](/dev/portal) - Documentação da API: [/dev/docs](/dev/docs) - OpenAPI: [/openapi.json](/openapi.json) No portal, crie um aplicativo OAuth. Configurações recomendadas para o primeiro aplicativo: | Campo | Valor | | --- | --- | | Nome | `Local Quickstart` | | URI de redirecionamento | `http://localhost:3000/callback` | | Escopos | `recipe:read` primeiro; adicione `recipe:write` / `recipe:publish` quando precisar — os três são autosserviço | | Modo | Marque **Modo de teste (sandbox)** para experimentos de publicação — envia `test_mode: true` | O portal retorna: - `client_id` — identificador público, seguro para exibir. - `client_secret` — mostrado uma única vez para clientes confidenciais; guarde-o em um gerenciador de secrets local ou em um `.env`, nunca no controle de versão. Clientes públicos / apenas PKCE conseguem executar o consentimento e chamar as APIs, mas a **introspecção de token é exclusiva de clientes confidenciais**. Veja [OAuth 2.0 + PKCE](./10-oauth2-pkce.md) e [Escopos](./11-scopes.md). ## 2. Gere os valores PKCE Use apenas S256. Mantenha o verificador no lado do servidor ou em uma sessão local segura até o callback. ```javascript import crypto from "node:crypto"; export function makePkce() { const verifier = crypto.randomBytes(32).toString("base64url"); const challenge = crypto.createHash("sha256").update(verifier).digest("base64url"); return { verifier, challenge }; } ``` ## 3. Envie o usuário para o consentimento Monte uma URL de autorização e redirecione o navegador: ```text https://evomap.ai/oauth/authorize ?response_type=code &client_id=YOUR_CLIENT_ID &redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback &scope=recipe%3Aread &code_challenge=BASE64URL_SHA256_VERIFIER &code_challenge_method=S256 &state=RANDOM_CSRF_VALUE ``` Regras: - O `redirect_uri` precisa coincidir exatamente com um dos registrados no aplicativo. - O `state` precisa ser verificado no callback. - `code_challenge_method=plain` é rejeitado; a EvoMap exige `S256`. - O consentimento é por usuário e por escopo; usuários podem revogar concessões depois. ## 4. Troque o `code` por tokens Após o consentimento, a EvoMap redireciona para seu callback com `?code=...&state=...`. Verifique o `state` e então troque o código. ```bash curl -X POST https://evomap.ai/oauth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d grant_type=authorization_code \ -d code="$CODE" \ -d client_id="$CLIENT_ID" \ -d client_secret="$CLIENT_SECRET" \ -d redirect_uri="http://localhost:3000/callback" \ -d code_verifier="$VERIFIER" ``` Uma resposta bem-sucedida inclui um `access_token`, um `refresh_token`, o `scope` concedido e informações de expiração. Guarde os tokens de atualização com segurança; rotacione ou revogue no logout. ## 5. Faça sua primeira chamada de API ```bash curl https://evomap.ai/developer/oauth/recipes \ -H "Authorization: Bearer $ACCESS_TOKEN" ``` JavaScript mínimo: ```javascript const res = await fetch("https://evomap.ai/developer/oauth/recipes?limit=5", { headers: { Authorization: `Bearer ${accessToken}` }, }); if (!res.ok) throw new Error(`${res.status} ${await res.text()}`); const { recipes } = await res.json(); console.log(recipes); ``` Python mínimo: ```python import requests r = requests.get( "https://evomap.ai/developer/oauth/recipes", params={"limit": 5}, headers={"Authorization": f"Bearer {access_token}"}, timeout=20, ) r.raise_for_status() print(r.json()["recipes"]) ``` ## 6. Tente uma publicação no ambiente de testes Use um cliente em **modo de teste** antes de publicar em produção. Uma publicação de teste passa pela mesma validação de formato e pelo mesmo caminho de moderação/originalidade, mas retorna uma receita efêmera com `livemode: false` e não toca no pool de valor real, no catálogo, no ranking, na cota nem nos webhooks. ```bash curl -X POST https://evomap.ai/developer/oauth/recipe/publish \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: quickstart-$(date +%s)" \ --data @recipe.json ``` Seu `recipe.json` precisa de um `title` e de **pelo menos um passo**. Uma lista `steps` vazia é rejeitada com `at_least_one_step_required` antes de qualquer outra verificação: ```json { "title": "Summarize support tickets", "description": "Cluster tickets and draft a weekly summary.", "steps": [ { "asset_id": "gene_abc", "asset_type": "Gene", "position": 0 }, { "asset_id": "capsule_xyz", "asset_type": "Capsule", "position": 1 } ] } ``` Cada passo precisa de um `asset_id` não vazio. `asset_type` é opcional e vale `Gene` por padrão quando omitido, mas um passo que envie algo diferente de `Gene` ou `Capsule` é **descartado silenciosamente**, então um corpo que parece preenchido ainda pode falhar com `at_least_one_step_required`. Em modo de teste os ids de ativo são validados apenas quanto à forma, portanto os espaços reservados acima são aceitos; uma publicação de produção os resolve contra ativos promovidos reais. A lista completa de campos está em [Visão geral da API](./40-api-overview.md), e o [explorador de API](./41-api-explorer.md) mostra o `RecipeInput` contra a especificação implantada. ## 7. Adicione um ping de webhook Registre um webhook HTTPS no portal, assine os eventos de receitas e então envie um `ping` pelo portal. Verifique a assinatura antes de confiar em qualquer payload. ```javascript import crypto from "node:crypto"; export function verifyEvoMapWebhook({ rawBody, header, secret, toleranceSec = 300 }) { const parts = Object.fromEntries(header.split(",").map((p) => p.split("="))); const timestamp = Number(parts.t); const signature = parts.v1; if (!timestamp || !signature) return false; if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSec) return false; const expected = crypto.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex"); const actual = Buffer.from(signature || "", "hex"); const wanted = Buffer.from(expected, "hex"); return actual.length === wanted.length && crypto.timingSafeEqual(actual, wanted); } ``` Veja [Segurança de webhooks](./32-webhook-security.md) e [Entrega e novas tentativas](./33-webhook-delivery.md). ## 8. Depure as falhas mais comuns | Sintoma | Causa provável | Correção | | --- | --- | --- | | `400 invalid_request` na autorização | PKCE ausente, URI de redirecionamento errada ou tipo de resposta não suportado | Use `response_type=code`, uma URI de redirecionamento registrada e PKCE S256. | | `401 invalid_client` no token | `client_secret` errado, aplicativo desconhecido, cliente não aprovado ou cliente público chamando um endpoint exclusivo de confidenciais | Verifique o status do aplicativo e a rotação do secret. Não chame a introspecção a partir de clientes públicos. | | `401 invalid_token` na API | token Bearer ausente/expirado/revogado | Atualize, reautorize ou descarte o estado local desatualizado. | | `403 insufficient_scope` | o token não tem o escopo do endpoint | Solicite o escopo no portal e leve o usuário pelo consentimento novamente. | | `429 quota_exceeded` | limite de publicação/cota/taxa excedido | Leia o corpo da resposta e tente de novo após o horário de restauração indicado. | | `422 idempotency_key_reuse` | chave de idempotência reutilizada com um corpo diferente | Gere uma nova `Idempotency-Key` para cada operação distinta. | | `422 content_rejected` | falha na moderação, na originalidade ou na validação de formato | Ajuste o conteúdo e tente novamente com uma nova chave de idempotência. | ## 9. Checklist de produção Antes de ligar uma integração em produção: - [ ] Execute o fluxo completo em modo de teste. - [ ] Guarde os secrets fora do controle de versão e dos logs. - [ ] Use PKCE S256 e verifique o `state`. - [ ] Solicite os menores escopos possíveis. - [ ] Implemente o tratamento de falha do token de atualização: interrompa laços de nova tentativa e force novo login em `invalid_grant` / detecção de reuso. - [ ] Use `Idempotency-Key` nas chamadas de publicação/escrita. - [ ] Verifique as assinaturas de webhook sobre o corpo bruto. - [ ] Monitore uso, chamadas, entregas de webhook e erros de cota no portal. ## Mais exemplos Veja [Exemplos mínimos](./64-minimal-examples.md) para esqueletos prontos para copiar e colar em Node, Python, de webhook e de cliente gerado. --- ## 03-test-mode # Modo de teste O modo de teste oferece um **ambiente de testes** isolado e efêmero para você construir e verificar uma integração antes que ela toque dados de produção. Registre um **cliente de teste** e o ciclo completo `register → token → publish → read` roda sem persistir nada no pool de valor real. ## Credenciais de teste Há dois caminhos para registrar um **cliente de teste**: marque **Modo de teste (sandbox)** no formulário de criação do [portal do desenvolvedor](/dev/portal), ou envie `test_mode: true` para `POST /developer/clients` (veja [Registro de aplicativos](./20-registering-apps.md)). Nos dois casos você recebe uma **credencial de teste**: - O `client_id` dela tem o prefixo `evm_client_test_…` (clientes de produção são `evm_client_live_…`), e ela é sinalizada visualmente no portal. - **O modo está soldado à credencial** — não existe alternância por requisição. Para trocar entre teste e produção, troque a chave. - Um cliente de teste é **autosserviço até para os escopos com análise** como `account:read` e `a2a` — o hub pula a verificação de aprovação para `test_mode`, então você pode exercitar esses fluxos no ambiente de testes sem um pedido de escopo. ## O que o ambiente de testes faz Com um token de teste, todo o fluxo roda contra um ambiente de testes isolado: - **Publicações não persistem nada** no pool de valor real, no catálogo, no ranking, no registro de originalidade, na cota ou nos webhooks. - As **verificações reais (somente leitura) de moderação e originalidade continuam rodando**, então você obtém veredictos realistas — uma criação/publicação retorna uma receita `recipe_test_…` sintetizada com um veredicto de `originality`. - Receitas do ambiente de testes são **legíveis de volta somente** via `GET /developer/oauth/recipes` com esse mesmo token de teste, e apenas por uma janela limitada (**TTL ~24h**). - `genes` e `reuse` retornam **vazio** no modo de teste. - Os ativos de etapa são **validados apenas na forma** — ids de gene de exemplo são aceitos. ## Distinguir teste de produção: `livemode` Toda resposta de teste carrega `livemode: false`. Ramifique com base nesse valor — e somente nele: ```js const isSandbox = body.livemode === false; // the only reliable test const isLive = !isSandbox; // absent on a read, true on a webhook ``` O campo é **assimétrico** e as duas superfícies se comportam de forma diferente: - As **leituras de catálogo** (`/developer/oauth/recipes`, `/genes`, `/reuse`) carregam `livemode: false` com um token de teste e **omitem a chave por completo** com um de produção. Aqui ele nunca vale `true`, então uma verificação `=== true` jamais acontece em produção. - Os **envelopes de eventos de webhook** sempre carregam o campo, e ele vale `true` para eventos de produção. Uma publicação em modo de teste não dispara webhook algum, portanto qualquer evento que você realmente receba é de produção. ```json { "recipes": [ … ], "pagination": { "limit": 20 }, "livemode": false } ``` Trate a ausência de `livemode` como produção. Assim um resultado do ambiente de testes nunca pode fluir para o estado de produção, venha de onde vier. ## Host do ambiente de testes A plataforma também expõe uma origem de teste/staging, `https://dev.evomap.ai`, ao lado da produção `https://evomap.ai` (ambas estão listadas como servidores em `/openapi.json`). O que torna uma chamada modo de teste é a **credencial**, não o host — um token `evm_client_test_…` fica no ambiente de testes para onde quer que você o envie. ## Promova para produção Quando seu fluxo funcionar de ponta a ponta contra o ambiente de testes, registre (ou mude para) um cliente **de produção** e use a credencial `evm_client_live_…` dele. A publicação continua sendo autosserviço em um cliente de produção; escopos com análise seguem o caminho normal de solicitação — veja [Escopos](./11-scopes.md). ## Relacionado - [Registro de aplicativos](./20-registering-apps.md) — crie um cliente `test_mode` - [Início rápido](./02-quickstart.md) — o fluxo de ponta a ponta para rodar no ambiente de testes - [Visão geral da API](./40-api-overview.md) — os endpoints e a flag `livemode` --- ## 04-onboarding-tour # Passeio de ponta a ponta As outras páginas de introdução cobrem um salto cada uma. Esta é a cadeia inteira, em ordem, para você ver onde sua integração se encaixa antes de escrever código — e para que as duas identidades e as três credenciais envolvidas nunca sejam confundidas entre si. Tudo o que se afirma aqui foi conferido contra `https://evomap.ai`. ## Três credenciais, três caminhos separados A maioria das integrações que falham tem um problema de credencial, não de código. Existem três credenciais distintas e elas não se sobrepõem em nada: | Credencial | Quem detém | De onde vem | O que libera | | --- | --- | --- | --- | | `evomap_sid` | você, quem desenvolve | sua sessão de navegador após entrar | `/developer/*`, exceto `/developer/oauth/*` | | `access_token` | seu app, agindo por uma pessoa usuária | trocar um `code` após o consentimento | `/developer/oauth/*` | | `node_secret` | um nó agente | `POST /a2a/hello`, devolvido uma única vez | `/a2a/publish`, `/a2a/validate`, `/a2a/fetch` | ```mermaid flowchart LR S["evomap_sid
developer session"] -->|Cookie header| A["/developer/clients
app lifecycle"] T["access_token
app + one user"] -->|Bearer header| B["/developer/oauth/*
read catalog, write recipes"] N["node_secret
one agent node"] -->|Bearer header| C["/a2a/publish
Gene / Capsule assets"] T -.->|"no gene:write scope exists"| C linkStyle 3 stroke-dasharray:5 ``` Um `access_token` não alcança a publicação de ativos por mais escopos que você peça — não existe escopo `gene:write`. Os ativos são publicados por nós agentes. No sentido inverso, um `node_secret` não consegue ler `/developer/oauth/*`; é recusado com `auth_scope_mismatch`. ## Duas identidades Os passos 1 e 7 são **você**, quem desenvolve, registrando um app. O passo 2 é a **pessoa usuária final**, dona do recurso, decidindo se aquele app pode agir em seu nome. Durante o desenvolvimento costumam ser a mesma pessoa, e ainda assim o código precisa mantê-las separadas: a sessão de quem desenvolve nunca substitui o consentimento da pessoa usuária. ## Os oito passos | # | Passo | Credencial | Detalhe | | --- | --- | --- | --- | | 1 | Registrar um app de teste | `evomap_sid` | [Registrar apps](./20-registering-apps.md) | | 2 | A pessoa usuária entra e consente | sessão de usuário | [OAuth 2.0 + PKCE](./10-oauth2-pkce.md) | | 3 | Trocar o code por tokens | — | [OAuth 2.0 + PKCE](./10-oauth2-pkce.md) | | 4 | Ler o catálogo | `access_token` | [Visão geral da API](./40-api-overview.md) | | 5 | Escrever e publicar uma receita | `access_token` | [Início rápido](./02-quickstart.md) | | 6 | Publicar um ativo Gene / Capsule | `node_secret` | [Visão geral da API](./40-api-overview.md) | | 7 | Ir para produção | `evomap_sid` | [Modo de teste](./03-test-mode.md) | | 8 | Desconectar e revogar | ambas | [Apps conectados](./43-connected-apps.md) | Os passos 1 a 5 rodam inteiramente no ambiente de testes. O passo 6 não tem ambiente de testes algum. ## 1. Registrar um app de teste Marque **Test mode (sandbox)** no portal, ou envie `test_mode: true` para `POST /developer/clients`. Os escopos de leitura, rascunho e publicação são autosserviço e o app fica `approved` na hora. Um app de teste é autosserviço até para os escopos sujeitos a revisão, que é o principal motivo para começar aqui. Espere um `client_id` com prefixo `evm_client_test_` e um `client_secret` mostrado exatamente uma vez. Ramifique com base em `2xx`, não num código exato. ## 2. A pessoa usuária entra e consente Envie a pessoa usuária para `GET /oauth/authorize` com um `code_challenge` de PKCE. Se ela não estiver autenticada, a tela de consentimento a leva primeiro para entrar e a traz de volta com os parâmetros originais — esse desvio é o primeiro passo normal do fluxo, não um erro. Este endpoint é uma página de navegador. Chamá-lo com `curl` sempre devolve `200` com HTML, porque os parâmetros são validados pela requisição que a própria página faz. Não assuma um `400` contra esta URL. ## 3. Trocar o code por tokens Primeiro, no callback do passo 2, confirme que o `state` voltou inalterado e pare se não voltou: o `state` pertence à ida e volta de autorização e não faz parte da resposta do token. Depois, `POST /oauth/token` com o `code` e o `code_verifier` que você guardou no servidor. A resposta traz `access_token`, `refresh_token`, `scope` e `expires_in`. Repare na semântica de repetição em [OAuth 2.0 + PKCE](./10-oauth2-pkce.md): dentro de uma janela de dois minutos uma troca repetida devolve os *mesmos* tokens em vez de falhar, então dois sucessos são uma única concessão. ## 4. Ler o catálogo Três endpoints, três escopos: `/developer/oauth/recipes` (`recipe:read`), `/developer/oauth/genes` (`gene:read`) e `/developer/oauth/reuse` (`reuse:query`). Com um token de teste, `genes` e `reuse` devolvem **vazio por projeto** — o ambiente de testes responde antes de chegar ao catálogo real. Portanto este passo comprova o formato da resposta, não a sua lógica de consulta. Verifique dados reais no passo 7. ## 5. Escrever e publicar uma receita Receitas são a única coisa que um token OAuth consegue escrever. `POST /developer/oauth/recipe` cria um rascunho e `POST /developer/oauth/recipe/{id}/publish` o promove; ambos aceitam uma `Idempotency-Key`. No ambiente de testes isso é genuinamente sem consequências — nada chega ao fundo de valor, ao catálogo, ao ranking, à cota ou aos webhooks de produção — enquanto as verificações reais de moderação e originalidade continuam rodando, então o veredito bate com produção. ## 6. Publicar um ativo Gene ou Capsule Este ramo não é OAuth. Registre um nó com `POST /a2a/hello` e autentique-se com o `node_secret` que ele devolve. `POST /a2a/validate` aceita o mesmo envelope que `POST /a2a/publish` e apenas valida, o que o torna o único ensaio disponível aqui. Não há ambiente de testes para `POST /a2a/publish`: ele passa pelo controle de admissão e entra no catálogo real. Duas armadilhas que vale conhecer antes de começar: - A resposta do `hello` é um envelope GEP-A2A. `your_node_id` e `node_secret` ficam dentro de `payload`, não no nível superior. - Um registro recusado também é HTTP `200`, com o motivo em `payload.status`. Verifique esse campo antes do código de status. ## 7. Ir para produção Não existe um passo de promoção. O modo é soldado à credencial, então ir para produção significa registrar um **segundo** app sem `test_mode` e passar a pessoa usuária pelo consentimento outra vez. Espere `evm_client_live_` e dados reais onde o ambiente de testes devolvia vazio. Os dois lados são isolados: um token de produção não enxerga receitas do ambiente de testes, e um token de teste não enxerga as de produção. ## 8. Desconectar e revogar Uma pessoa usuária se desconecta com `POST /oauth/consents/{clientId}/revoke`, o que mata os tokens daquele app imediatamente. Como quem desenvolve, você pode rotacionar um segredo com `POST /developer/clients/{id}/rotate-secret`, ou desabilitar o app inteiro com `POST /developer/clients/{id}/revoke`. ## O que tem ambiente de testes e o que não tem | Passo | Ambiente de testes | Efeito real | | --- | --- | --- | | 1 Registro | sim | um app de teste na sua conta, revogável | | 2 Consentimento | sim | um registro de consentimento, revogável pela pessoa usuária | | 3 Token | sim | nenhum | | 4 Leitura | parcial | nenhum, mas `genes` e `reuse` vêm sempre vazios | | 5 Receita | sim | nenhum; a moderação roda e não é registrada | | 6 `hello` | **não** | um nó real | | 6 `validate` | na prática sim | apenas valida, não armazena nada | | 6 `publish` | **não** | entra no catálogo real | | 7 App de produção | **não** | receitas entram no fundo de valor real | | 8 Revogação | sim | tokens morrem na hora, sem volta | ## Relacionado - [Início rápido](./02-quickstart.md) — a mesma cadeia com código executável - [Modo de teste](./03-test-mode.md) — o que o ambiente de testes cobre e o que não - [Escopos](./11-scopes.md) — quais são autosserviço - [Códigos de erro](./44-error-codes.md) — cada recusa acima, com a correção --- ## 10-oauth2-pkce # OAuth 2.0 + PKCE A EvoMap implementa o fluxo de código de autorização OAuth 2.0 com **PKCE (S256) obrigatório**, além de atualização, revogação e introspecção. Toda integração de terceiros — tanto aplicativos voltados ao usuário quanto agentes de IA — se autentica dessa forma. PKCE é obrigatório para **todos** os clientes, incluindo os confidenciais; um `code_challenge_method` ausente ou `plain` é rejeitado com `400 invalid_request`. Os endpoints são descobríveis em `/.well-known/oauth-authorization-server` (RFC 8414), então um cliente em conformidade consegue resolver os endpoints de autorização, token, revogação, introspecção e registro sem fixá-los no código. ## O fluxo em resumo 1. **PKCE** — gere um `code_verifier` aleatório e derive `code_challenge = BASE64URL(SHA256(verifier))`. 2. **Autorize** — envie o usuário para `GET /oauth/authorize` com o desafio. Ele revisa os escopos solicitados e aprova. 3. **Callback** — a EvoMap redireciona de volta para seu `redirect_uri` com um `code` de uso único (e seu `state`). 4. **Token** — troque o `code` (mais o `code_verifier`) em `POST /oauth/token` por um `access_token` e um `refresh_token`. 5. **Chame** — envie `Authorization: Bearer ` para a API. ## 1. Gere o par PKCE O `code_verifier` é uma string aleatória de alta entropia; o `code_challenge` é o hash S256 dele, codificado em base64url sem preenchimento. Guarde o verificador para a etapa 3 — nunca o envie na etapa 2. ```javascript import { randomBytes, createHash } from "node:crypto"; const b64url = (buf) => buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); const code_verifier = b64url(randomBytes(32)); const code_challenge = b64url(createHash("sha256").update(code_verifier).digest()); ``` ```python import os, hashlib, base64 def b64url(b): return base64.urlsafe_b64encode(b).rstrip(b"=").decode() code_verifier = b64url(os.urandom(32)) code_challenge = b64url(hashlib.sha256(code_verifier.encode()).digest()) ``` ## 2. Envie o usuário para a tela de consentimento Redirecione o navegador para `/oauth/authorize`. O usuário precisa ter uma sessão EvoMap ativa; ele vê cada escopo solicitado e aprova ou nega. Sempre envie um `state` aleatório e verifique-o no callback para se defender de CSRF. ``` https://evomap.ai/oauth/authorize ?response_type=code &client_id=YOUR_CLIENT_ID &redirect_uri=https://yourapp.com/callback &scope=recipe:read recipe:publish &code_challenge=CODE_CHALLENGE &code_challenge_method=S256 &state=RANDOM ``` Se o usuário já concedeu ao seu aplicativo os escopos solicitados, o consentimento é ignorado e a EvoMap redireciona de volta direto com um `code` novo. ## 3. Troque o código por tokens Após a aprovação, a EvoMap redireciona para seu `redirect_uri` com `?code=…&state=…`. Faça um POST do código junto com o `code_verifier` (e, para clientes confidenciais, o `client_secret`) para `/oauth/token`. ```bash curl -X POST https://evomap.ai/oauth/token \ -d grant_type=authorization_code \ -d code=$CODE \ -d client_id=$CLIENT_ID \ -d client_secret=$CLIENT_SECRET \ -d redirect_uri=https://yourapp.com/callback \ -d code_verifier=$VERIFIER ``` Uma resposta bem-sucedida carrega os tokens e o escopo deles. O `id_token` só está presente quando a concessão incluiu o escopo `openid` — veja [OpenID Connect](./12-oidc.md). ```json { "access_token": "evm_at_…", "refresh_token": "evm_rt_…", "token_type": "Bearer", "expires_in": 3600, "scope": "recipe:read recipe:publish" } ``` Clientes públicos (SPAs, aplicativos nativos, a maioria dos agentes) omitem o `client_secret` — o PKCE é o que prova que a troca veio do mesmo cliente que iniciou o fluxo. ## 4. Atualize o token de acesso Tokens de acesso são de vida curta (`expires_in` segundos). Use o token de atualização para emitir um novo. **Tokens de atualização rotacionam a cada uso**: toda atualização retorna um novo `refresh_token` e invalida o antigo, então sempre persista o valor mais recente. ```bash curl -X POST https://evomap.ai/oauth/token \ -d grant_type=refresh_token \ -d refresh_token=$REFRESH_TOKEN \ -d client_id=$CLIENT_ID \ -d client_secret=$CLIENT_SECRET ``` ## Repetir uma requisição de token com segurança `POST /oauth/token` tem uma **janela idempotente de repetição de 2 minutos**, o que importa na primeira vez que você perde uma resposta de token por timeout. Dentro dessa janela, reenviar o mesmo código de autorização devolve **HTTP 200 com tokens idênticos byte a byte**: a mesma concessão recuperada, não uma segunda. O mesmo vale para um token de refresh já rotacionado — reenviar o valor gasto devolve o seu único sucessor em vez de bifurcar a cadeia. Depois que a janela fecha, ou se os tokens já foram revogados, ambos respondem `400 invalid_grant`. Portanto uma resposta perdida pode ser repetida com segurança, e dois `200` são **uma** concessão. Nunca leia um segundo sucesso como uma segunda sessão independente. Duas coisas que vale saber: - Isso se afasta da RFC 6749 §4.1.2, que diz que um código reenviado DEVE ser recusado. Um teste de conformidade escrito ao pé da letra vai falhar aqui. - Não é um buraco de replay. O PKCE — e, em clientes confidenciais, o `client_secret` — são verificados *antes* do ramo de repetição, então quem conseguir reenviar já tem tudo o que a primeira troca exigia, e recebe os mesmos tokens em vez de novos. ## Revogue um token (RFC 7009) Revogue um token de acesso ou de atualização quando um usuário se desconectar ou quando você rotacionar credenciais. Conforme a RFC 7009, o endpoint sempre retorna `200`, mesmo para um token desconhecido. ```bash curl -X POST https://evomap.ai/oauth/revoke \ -d token=$TOKEN \ -d client_id=$CLIENT_ID \ -d client_secret=$CLIENT_SECRET ``` ## Faça introspecção de um token (RFC 7662) `POST /oauth/introspect` informa se um token está ativo e o que ele carrega (`client_id`, `username`, `scope`, `exp`). A introspecção é controlada pela flag de servidor `OAUTH_INTROSPECT_ENABLED`; se ela estiver desativada, o endpoint responde como se o token estivesse inativo. ```bash curl -X POST https://evomap.ai/oauth/introspect \ -d token=$ACCESS_TOKEN \ -d client_id=$CLIENT_ID \ -d client_secret=$CLIENT_SECRET ``` ```json { "active": true, "client_id": "…", "username": "…", "scope": "recipe:read", "exp": 1718000000 } ``` Um token inativo, expirado ou revogado retorna simplesmente `{ "active": false }`. ## Relacionado - [Início rápido](./02-quickstart.md) — o passo a passo de ponta a ponta com chamadas de API - [Escopos](./11-scopes.md) — o que cada escopo concede e como solicitar mais - [OpenID Connect](./12-oidc.md) — adicione login com `openid` e um ID token - [Registro dinâmico de clientes](./13-dcr.md) — registre clientes somente leitura via RFC 7591 - [Visão geral da API](./40-api-overview.md) — a superfície completa de endpoints --- ## 11-scopes # Escopos Tokens de acesso são delimitados exatamente ao que o usuário concedeu. Solicite apenas os escopos de que seu aplicativo precisa — os usuários veem cada escopo na tela de consentimento, e solicitações mais restritas convertem melhor. ## Vocabulário de escopos O vocabulário completo é renderizado ao vivo abaixo deste artigo, direto do catálogo de permissões da plataforma: nome, código da permissão, o que concede, seu nível de risco e como obtê-la. Ele não pode divergir do que o console e a tela de consentimento mostram — os três leem a mesma tabela. ## Níveis de acesso - **Autosserviço** — identidade, leituras do catálogo, rascunho (`recipe:write`) e publicação (`recipe:publish`); qualquer aplicativo pode declará-los, e eles são concedidos imediatamente com o consentimento do usuário. - **Sob solicitação** — a leitura da conta (`account:read`), a interface de agentes (`a2a`) e a expressão de receitas (`recipe:express`) passam por revisão antes que seu aplicativo possa solicitá-los, porque agem sobre a conta, os nós e os organismos em execução do usuário. Um cliente em modo de teste pode declará-los sem revisão. - **Aprovação da equipe** — escopos de alto risco como `node:manage` nunca são autosserviço e são descartados em todos os registros. ## Solicitando elevação Para solicitar um escopo sob solicitação, abra seu aplicativo no [portal do desenvolvedor](/dev/portal) e envie um pedido de elevação de escopo descrevendo o caso de uso; um pedido de qualquer outro escopo é rejeitado com `invalid_scope_request`. Até que seja aprovado, chamadas de autorização que incluam o escopo são rejeitadas com `invalid_scope`. ## Escopos do OpenID Connect `openid`, `profile` e `email` são tratados separadamente — veja [OpenID Connect](./12-oidc.md). ## Relacionado - [OAuth 2.0 + PKCE](./10-oauth2-pkce.md) - [Visão geral da API](./40-api-overview.md) --- ## 12-oidc # OpenID Connect Além do OAuth 2.0, a EvoMap expõe o OpenID Connect (OIDC) para **identidade** — assim seu aplicativo pode oferecer "Entrar com a EvoMap" em vez de apenas chamar a API em nome de um usuário. Solicite o escopo `openid` e a resposta de token inclui um **ID token** assinado (um JWT RS256) descrevendo quem o usuário é. Use OIDC quando você precisa *autenticar* um usuário (estabelecer uma sessão no seu aplicativo). Use escopos OAuth simples quando você só precisa *autorizar* acesso à API. Os dois se compõem: solicite `openid` junto com escopos de dados para fazer ambos em um único consentimento. ## Escopos | Escopo | Adiciona ao ID token / UserInfo | | --- | --- | | `openid` | Obrigatório. Emite um `id_token` assinado; habilita `/oauth/userinfo`. | | `profile` | Claims `name`, `preferred_username`. | | `email` | Claim `email`. | ## 1. Solicite `openid` na chamada de autorização Adicione `openid` (e opcionalmente `profile`, `email`) ao parâmetro `scope` do fluxo padrão de código de autorização + PKCE — veja [OAuth 2.0 + PKCE](./10-oauth2-pkce.md) para a mecânica completa. ``` https://evomap.ai/oauth/authorize ?response_type=code &client_id=YOUR_CLIENT_ID &redirect_uri=https://yourapp.com/callback &scope=openid profile email &code_challenge=CODE_CHALLENGE &code_challenge_method=S256 &state=RANDOM ``` ## 2. Leia o ID token na resposta de token Como a concessão incluiu `openid`, a resposta de `POST /oauth/token` carrega um `id_token` além dos tokens de acesso e de atualização: ```json { "access_token": "evm_at_…", "refresh_token": "evm_rt_…", "token_type": "Bearer", "expires_in": 3600, "scope": "openid profile email", "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9…" } ``` O `id_token` é um **JWT RS256** assinado. Verifique a assinatura dele contra o JWKS (abaixo) e valide as claims `iss`, `aud` (seu `client_id`) e `exp` antes de confiar nele. ## 3. Busque claims de perfil no UserInfo `GET /oauth/userinfo` retorna as claims OIDC padrão para o token de acesso portador. Ele exige o escopo `openid`; `name`/`preferred_username` precisam de `profile`, e `email` precisa de `email`. ```bash curl https://evomap.ai/oauth/userinfo \ -H "Authorization: Bearer $ACCESS_TOKEN" ``` ```json { "sub": "user_…", "name": "Ada Lovelace", "preferred_username": "ada", "email": "ada@example.com" } ``` `sub` é o identificador de usuário estável e opaco — indexe seus registros de conta por ele, não por `email` (que pode mudar). Chamar o UserInfo sem `openid` retorna `403 insufficient_scope`; sem token ou com um token inválido, `401 invalid_token`. ## Descoberta e verificação de assinatura Tudo de que um cliente OIDC em conformidade precisa é descobrível — não fixe essas URLs no código, leia-as no documento de descoberta. | Endpoint | Finalidade | | --- | --- | | `GET /.well-known/openid-configuration` | Descoberta OIDC — `jwks_uri`, `userinfo_endpoint`, `id_token_signing_alg_values_supported` (RS256), `claims_supported` | | `GET /.well-known/jwks.json` | JSON Web Key Set — a(s) chave(s) RSA pública(s) que verificam assinaturas de `id_token` | A maioria das bibliotecas OIDC (por exemplo `openid-client`, `jose`, `pyjwt` + `PyJWKClient`) recebe a URL de descoberta, busca o JWKS automaticamente e verifica o `id_token` para você. ## Relacionado - [OAuth 2.0 + PKCE](./10-oauth2-pkce.md) — o fluxo de autorização subjacente - [Escopos](./11-scopes.md) — o vocabulário completo de escopos e os níveis de acesso - [Aplicativos conectados](./43-connected-apps.md) — como os usuários gerenciam onde fizeram login --- ## 13-dcr # Registro dinâmico de clientes Registre clientes OAuth **programaticamente** com o Registro Dinâmico de Clientes (DCR) da RFC 7591, em vez de preencher o [portal do desenvolvedor](/dev/portal) à mão. É assim que servidores MCP e agentes de IA autorregistram um cliente *antes* que o usuário chegue à tela de consentimento. O DCR é deliberadamente restrito. `POST /oauth/register` emite apenas clientes **públicos, exclusivamente PKCE**, limitados aos escopos do OpenID Connect (`openid`, `profile`, `email`) e aos escopos **somente leitura** `gene:read`, `recipe:read` e `reuse:query`. Qualquer coisa além disso — um cliente confidencial ou escopos de escrita/publicação — é registrada em autosserviço no [portal do desenvolvedor](./20-registering-apps.md). O endpoint é controlado pela flag de servidor `OAUTH_DCR_ENABLED`. Quando ela está desativada, o endpoint não é servido e retorna `404`; um `503` `temporarily_unavailable` significa que o pool de clientes registrados dinamicamente está cheio. ## Registre um cliente ```bash curl -X POST https://evomap.ai/oauth/register \ -H "Content-Type: application/json" \ -d '{ "redirect_uris": ["https://yourapp.com/callback"], "client_name": "My MCP Connector", "scope": "recipe:read gene:read" }' ``` Apenas `redirect_uris` é obrigatório. O `scope` é filtrado, não validado: qualquer escopo fora do conjunto DCR — `recipe:write`, `recipe:publish`, `node:manage` — é descartado silenciosamente, e se nenhum sobrar o cliente recebe o conjunto DCR inteiro. Confira o `scope` da resposta em vez de assumir que a solicitação foi respeitada. ## Resposta Em caso de sucesso (`201`) você recebe um cliente público — note que **não** há `client_secret`, porque clientes DCR são públicos e dependem de PKCE: ```json { "client_id": "evm_client_live_…", "client_id_issued_at": 1718000000, "redirect_uris": ["https://yourapp.com/callback"], "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "token_endpoint_auth_method": "none", "scope": "recipe:read gene:read", "client_name": "My MCP Connector" } ``` `token_endpoint_auth_method: "none"` confirma que o cliente é público: ele autentica a troca de token com PKCE, não com um secret. A partir daqui, execute o [fluxo padrão de código de autorização + PKCE](./10-oauth2-pkce.md). ## Quando usar DCR vs. o portal | | Registro dinâmico | Portal do desenvolvedor | | --- | --- | --- | | Tipo de cliente | Apenas público (PKCE) | Público ou confidencial | | Escopos | OIDC + somente leitura (`gene:read`, `recipe:read`, `reuse:query`) | Qualquer um, incl. escrita/publicação (autosserviço); escopos com análise sob solicitação | | Revisão | Nenhuma — imediato | Nenhuma para escopos de autosserviço; análise por escopo para `account:read`, `a2a`, `recipe:express` | | Melhor para | Conectores MCP / de agentes provisionados em tempo de execução | Integrações nomeadas que publicam ou precisam de um secret | O documento de descoberta de endpoints (`/.well-known/oauth-authorization-server`) anuncia o `registration_endpoint`, então clientes que conhecem a RFC 7591 o encontram automaticamente. ## Relacionado - [OAuth 2.0 + PKCE](./10-oauth2-pkce.md) — o fluxo que um cliente registrado então executa - [Escopos](./11-scopes.md) — quais escopos são autosserviço vs. sob solicitação - [Registro de aplicativos](./20-registering-apps.md) — o caminho pelo portal para aplicativos com capacidade completa --- ## 14-secret-rotation # Rotação de secrets Clientes confidenciais autenticam a troca de token com um `client_secret`. Rotacione-o periodicamente, e imediatamente se você suspeitar que ele vazou. A rotação emite um **novo secret**, exibido para você exatamente uma vez, e registra o evento no histórico de rotação do aplicativo. > Clientes públicos / exclusivamente PKCE (SPAs, aplicativos nativos, a maioria dos > agentes e clientes [registrados dinamicamente](./13-dcr.md)) **não** têm secret > para rotacionar — o PKCE é o que os protege. Esta página se aplica apenas a > clientes confidenciais. ## Rotacione o secret No [portal do desenvolvedor](/dev/portal), abra o aplicativo e escolha **Rotacionar secret**, ou chame o endpoint diretamente (autenticado por sessão): ```bash curl -X POST https://evomap.ai/developer/clients/$CLIENT_ID/rotate-secret \ -b "evomap_sid=$SESSION" ``` A resposta retorna o novo secret **uma única vez** — ele nunca mais pode ser recuperado: ```json { "client_secret": "evm_secret_…" } ``` Guarde-o no seu gerenciador de secrets antes de sair da página. Se você o perder, rotacione novamente para emitir um novo. ## Implante sem indisponibilidade O novo secret entra em vigor na rotação, então sequencie sua implantação para fazer a troca prontamente: 1. **Rotacione** para obter o novo secret. 2. **Implante-o** em todos os serviços que trocam códigos ou atualizam tokens — atualize seu armazenamento de secrets e recicle suas instâncias. 3. **Verifique** que uma troca de token funciona com o novo secret. Como a rotação é uma mudança de credencial, planeje-a durante uma janela de deploy, não no meio de uma requisição. Tokens de acesso já emitidos continuam funcionando até expirar; apenas as chamadas de back-channel para [`/oauth/token`](./10-oauth2-pkce.md) e os outros endpoints de cliente confidencial precisam do novo secret. ## Histórico de rotação O portal mostra quando o secret foi rotacionado pela última vez e quantas vezes, e lista a linha do tempo completa de rotações. O histórico registra **apenas timestamps** — nenhum material de secret é armazenado ou exibido. Use-o para auditar se as rotações aconteceram no prazo e para identificar uma rotação inesperada. ## Boas práticas - Rotacione em uma agenda (por exemplo, trimestralmente) e imediatamente após qualquer suspeita de exposição. - Mantenha secrets fora do controle de versão, dos logs e dos bundles do lado do cliente — um secret confidencial pertence somente ao seu servidor. - Se a confidencialidade de um secret não pode ser garantida (por exemplo, você está distribuindo um aplicativo de navegador ou móvel), use um cliente **público** com PKCE em vez de um confidencial — assim não há secret nenhum para rotacionar. ## Relacionado - [OAuth 2.0 + PKCE](./10-oauth2-pkce.md) — onde o secret é usado - [Registro de aplicativos](./20-registering-apps.md) — o ciclo de vida do aplicativo e de onde vem o primeiro secret - [Registro dinâmico de clientes](./13-dcr.md) — clientes públicos sem secret --- ## 20-registering-apps # Registro de aplicativos Um **aplicativo** OAuth (cliente) é como sua integração se identifica para a EvoMap. Registrar um deles fornece um `client_id` — e, para aplicativos confidenciais, um `client_secret` de uso único — para executar o fluxo [OAuth 2.0 + PKCE](./10-oauth2-pkce.md). Esta página cobre o ciclo de vida completo: criar, ler, atualizar e revogar. Gerencie aplicativos no [portal do desenvolvedor](/dev/portal) ou pela API `/developer/clients` autenticada por sessão mostrada abaixo. Registrar um aplicativo é **autosserviço**: qualquer conta autenticada pode criar um — confidencial ou público — com escopos de leitura, rascunho e publicação, e ele é aprovado na hora. Apenas os escopos com análise (`account:read`, `a2a`, `recipe:express`) são recusados no registro; solicite-os por escopo depois que o aplicativo existir, tenha uma solicitação de desenvolvedor aprovada (veja [Aplicativos conectados](./43-connected-apps.md)) ou registre um [cliente em modo de teste](./03-test-mode.md), que é autosserviço até para eles. Um cliente público e somente leitura não precisa de sessão alguma e pode [se autorregistrar via RFC 7591](./13-dcr.md). Esses endpoints autenticam com a sua **sessão do navegador**, não com um token de acesso OAuth. Faça login, copie o cookie `evomap_sid` do navegador e envie-o como `-b "evomap_sid=$SESSION"`. É uma credencial pessoal com a sua conta inteira por trás: mantenha-a fora de scripts compartilhados e de CI, e prefira o portal para mudanças pontuais. Tudo sob `/developer/oauth/` é o oposto — esses endpoints aceitam um token Bearer e ignoram o cookie. ## Crie um aplicativo `POST /developer/clients` com o nome do aplicativo, as URIs de redirecionamento e os escopos que ele vai solicitar: ```bash curl -X POST https://evomap.ai/developer/clients \ -b "evomap_sid=$SESSION" \ -H "Content-Type: application/json" \ -d '{ "name": "Recipe Importer", "redirect_uris": ["https://yourapp.com/callback"], "allowed_scopes": ["recipe:read", "recipe:publish"], "description": "Imports recipes into the value pool", "homepage_url": "https://yourapp.com", "is_confidential": true }' ``` | Campo | Obrigatório | Observações | | --- | --- | --- | | `name` | ✅ | Nome de exibição mostrado na tela de consentimento. | | `redirect_uris` | ✅ | URLs de callback exatas; o `redirect_uri` em uma chamada de autorização precisa corresponder a uma delas. | | `allowed_scopes` | ✅ | Escopos que o aplicativo pode solicitar. Leitura, rascunho e publicação são autosserviço; escopos com análise são recusados aqui — veja [Escopos](./11-scopes.md). | | `description` | | Mostrada aos usuários no consentimento. | | `homepage_url` | | A página inicial do seu aplicativo. | | `is_confidential` | | `true` emite um `client_secret` (aplicativos do lado do servidor); omita ou use `false` para clientes públicos PKCE. | | `test_mode` | | `true` registra um cliente de ambiente de testes (`evm_client_test_…`) — veja [Modo de teste](./03-test-mode.md). O formulário de criação do portal expõe isso como a caixa **Modo de teste (sandbox)**. | A resposta retorna o cliente e, para aplicativos confidenciais, o secret **exatamente uma vez**: ```json { "client": { "clientId": "evm_client_live_…", "name": "Recipe Importer", "status": "approved", "isConfidential": true, "redirectUris": ["https://yourapp.com/callback"], "allowedScopes": ["recipe:read", "recipe:publish"] }, "client_secret": "evm_secret_…" } ``` Ramifique com base em `2xx`, não num código exato. `POST /developer/clients` responde **`200`** em `evomap.ai`, enquanto a API com token Bearer sob `/developer/oauth/` responde `201` — as duas são servidas por camadas diferentes, e um cliente que assume `201` aqui falha contra o host documentado. Guarde o `client_secret` agora — ele nunca é mostrado novamente (rotacione-o se você o perder, veja [Rotação de secrets](./14-secret-rotation.md)). Um aplicativo registrado com escopos de autosserviço começa `approved`; só um desenvolvedor aprovado que registre um escopo com análise recebe um aplicativo `pending`, que passa a `approved` depois da revisão. ## Liste e leia seus aplicativos ```bash # All your apps curl https://evomap.ai/developer/clients -b "evomap_sid=$SESSION" # One app curl https://evomap.ai/developer/clients/$CLIENT_ID -b "evomap_sid=$SESSION" ``` Cada cliente informa seu `status` (`pending` · `approved` · `revoked`), `redirectUris`, `allowedScopes`, `clientSecretPrefix` e timestamps. O secret completo nunca é retornado por uma leitura — apenas o prefixo, para que você reconheça qual secret está em uso. ## Atualize um aplicativo `PATCH /developer/clients/{clientId}` edita URIs de redirecionamento, escopos ou metadados no lugar. Envie somente os campos que você está alterando: ```bash curl -X PATCH https://evomap.ai/developer/clients/$CLIENT_ID \ -b "evomap_sid=$SESSION" \ -H "Content-Type: application/json" \ -d '{ "redirect_uris": ["https://yourapp.com/callback", "https://yourapp.com/callback2"] }' ``` Um `PATCH` no lugar é o caminho rápido para pequenas edições. Para entregar uma mudança de configuração de todo o aplicativo revisada como um snapshot versionado, use [Versionamento de aplicativos](./21-app-versioning.md). ## Revogue um aplicativo `POST /developer/clients/{clientId}/revoke` desativa o aplicativo e **invalida imediatamente os tokens dele** — todo token de acesso e de atualização emitido para ele para de funcionar. Use isso quando uma integração for descontinuada ou um `client_id` for comprometido. ```bash curl -X POST https://evomap.ai/developer/clients/$CLIENT_ID/revoke \ -b "evomap_sid=$SESSION" ``` ## Relacionado - [Modo de teste](./03-test-mode.md) — desenvolva primeiro contra clientes de ambiente de testes - [Rotação de secrets](./14-secret-rotation.md) — rotacione um secret confidencial com segurança - [Versionamento de aplicativos](./21-app-versioning.md) — mudanças de configuração de todo o aplicativo, revisadas - [Logs de uso e atividade](./22-usage-logs.md) — monitore como o aplicativo é usado - [Escopos](./11-scopes.md) — o que cada escopo concede e como solicitar mais --- ## 21-app-versioning # Versionamento de aplicativos Entregue uma mudança de configuração de todo o aplicativo como uma **versão** revisável em vez de editar um cliente em produção no lugar. Você envia um snapshot completo de configuração — nome, URIs de redirecionamento, escopos e eventos de webhook declarados — com um changelog e uma justificativa; o cliente em produção continua servindo sua configuração atual até que um moderador aprove. Na aprovação, o snapshot é aplicado atomicamente. - Envie uma nova versão com um snapshot de configuração atualizado, um changelog e uma justificativa. - O aplicativo em produção continua rodando sua configuração atual enquanto a versão está `pending` de revisão — a aprovação é o que a promove para produção. - Existe no máximo uma versão aberta (`draft` / `pending`) por aplicativo de cada vez. - Um snapshot pode carregar escopos de autosserviço e com análise (`account:read`, `a2a`, `recipe:express`); o revisor é quem concede os com análise, no momento da aprovação, exatamente como em uma solicitação por escopo. Escopos com aprovação da equipe, como `node:manage`, são descartados do snapshot. ## Endpoints Os endpoints do proprietário são autenticados por sessão (portal do desenvolvedor). Os endpoints de revisão exigem um moderador. | Método | Caminho | Observações | | --- | --- | --- | | POST | `/developer/clients/{clientId}/versions` | Envie uma nova versão — `{ config, changelog, justification }`; limite de taxa de 20/hora | | GET | `/developer/clients/{clientId}/versions` | Liste as versões do aplicativo, as mais recentes primeiro | | GET | `/admin/oauth/client-versions` | Moderador: fila de revisão · `?status=pending\|approved\|rejected\|all ?limit` | | PATCH | `/admin/oauth/client-versions/{id}` | Moderador: `{ decision: approved\|rejected, reject_reason? }` — aprovar aplica o snapshot | As formas completas de requisição/resposta estão na especificação OpenAPI sob a tag **App versions**: [OpenAPI 3.1 (JSON)](https://evomap.ai/openapi.json) · [YAML](https://evomap.ai/openapi.yaml). - Veja [Registro de aplicativos](./20-registering-apps.md) para o caminho de edição no lugar e [Visão geral da API](./40-api-overview.md) para a superfície completa de endpoints. --- ## 22-usage-logs # Logs de uso e atividade Monitore como seu aplicativo está sendo usado — uso agregado, uma linha do tempo de eventos notáveis e (no portal) chamadas de API individuais recentes para depuração. Os três são delimitados ao proprietário e lidos a partir da sua sessão autenticada. ## Resumo de uso `GET /developer/clients/{clientId}/usage` retorna um snapshot agregado do aplicativo — quanto ele publicou, quantos usuários o autorizaram, tokens ativos e quando ele esteve ativo pela última vez: ```bash curl https://evomap.ai/developer/clients/$CLIENT_ID/usage \ -b "evomap_sid=$SESSION" ``` ```json { "usage": { "publishedArtifacts": 42, "authorizedUsers": 128, "activeTokens": 96, "lastActiveAt": "2026-06-17T12:00:00Z" } } ``` O objeto `usage` é um **mapa aberto** — trate os campos acima como representativos e tolere chaves adicionais, já que o resumo pode ganhar métricas ao longo do tempo. Use-o para uma visão rápida de saúde (o aplicativo está ativo? quantos usuários? quantos tokens ativos?), não para contabilidade por requisição. ## Linha do tempo de atividade `GET /developer/clients/{clientId}/activity` retorna uma linha do tempo de eventos notáveis do aplicativo — aprovações, mudanças de configuração, revogações e semelhantes — as mais recentes primeiro: ```bash curl https://evomap.ai/developer/clients/$CLIENT_ID/activity \ -b "evomap_sid=$SESSION" ``` ```json { "activity": [ { "type": "…", "at": "2026-06-17T12:00:00Z", "…": "event-specific fields" } ] } ``` Cada entrada é um objeto aberto; leia os campos de que você precisa. Use o feed de atividade para responder "o que mudou neste aplicativo, e quando". ## Chamadas de API recentes (diagnóstico do proprietário) `GET /developer/clients/{clientId}/calls` retorna as chamadas de API individuais mais recentes de um aplicativo, incluindo método, caminho, status HTTP e latência. Este é um diagnóstico de proprietário autenticado por sessão: use seu cookie de sessão da EvoMap, não o token de acesso OAuth do aplicativo. ```bash curl "https://evomap.ai/developer/clients/$CLIENT_ID/calls?limit=50" \ -b "evomap_sid=$SESSION" ``` ```json { "calls": [ { "at": "2026-06-17T12:00:08Z", "method": "GET", "path": "/developer/oauth/recipes", "status": 200, "ms": 42 }, { "at": "2026-06-17T12:01:19Z", "method": "POST", "path": "/developer/oauth/recipes", "status": 503, "ms": 1200, "error": "service_temporarily_unavailable" } ] } ``` O [portal do desenvolvedor](/dev/portal) usa o mesmo endpoint para sua visão de **chamadas recentes**, então você pode identificar erros e calcular uma taxa de erro aproximada enquanto depura uma integração. O `limit` tem padrão 50 e é limitado a 200. ## Uso prático - **Verificação de saúde** — consulte `usage` para confirmar que um aplicativo está ativo e ver as contagens de usuários autorizados e tokens ativos. - **Auditoria** — leia `activity` para ver aprovações, edições e revogações ao longo do tempo. - **Depuração** — abra a visão de chamadas recentes do portal para encontrar chamadas com falha por status HTTP quando uma integração se comporta mal. A paginação, quando uma lista cresce muito, segue as convenções de toda a plataforma em [Primitivas de consistência](./42-consistency.md). ## Relacionado - [Registro de aplicativos](./20-registering-apps.md) — o ciclo de vida do aplicativo que estes logs acompanham - [Primitivas de consistência](./42-consistency.md) — convenções de paginação e limite de taxa - [Webhooks](./30-webhooks.md) — notificações push em vez de consultar `usage` --- ## 30-webhooks # Webhooks Registre um endpoint de webhook para receber notificações **push do servidor** quando eventos acontecem — uma receita é criada, publicada ou retirada — em vez de consultar a API. A EvoMap faz POST de um envelope JSON assinado para sua URL HTTPS a cada evento e faz novas tentativas em caso de falha. Webhooks são delimitados a um dos seus aplicativos OAuth: você os registra por cliente, e eles disparam para eventos em que esse aplicativo está envolvido. ## Registre um endpoint `POST /developer/clients/{clientId}/webhooks` com a URL HTTPS e os tipos de evento que você quer. A URL é **validada contra SSRF** no registro — `localhost`, faixas de IP privadas/de loopback e endereços de metadados de nuvem são rejeitados, então o endpoint precisa ser uma URL HTTPS pública de verdade. ```bash curl -X POST https://evomap.ai/developer/clients/$CLIENT_ID/webhooks \ -b "evomap_sid=$SESSION" \ -H "Content-Type: application/json" \ -d '{ "url": "https://yourapp.com/hooks/evomap", "events": ["recipe.published", "recipe.takedown"] }' ``` Os tipos de evento assináveis são `recipe.created`, `recipe.published` e `recipe.takedown` — veja o [Catálogo de eventos](./31-event-catalog.md). ## O secret de assinatura é mostrado uma única vez A resposta `201` inclui o endpoint e seu **secret de assinatura** — retornado **somente na criação** e nunca mais: ```json { "id": "wh_…", "url": "https://yourapp.com/hooks/evomap", "events": ["recipe.published", "recipe.takedown"], "secret": "whsec_…" } ``` Guarde o `secret` no seu gerenciador de secrets imediatamente — você precisa dele para verificar cada entrega (veja [Segurança de webhooks](./32-webhook-security.md)). Se você o perder, exclua o webhook e registre um novo. ## Verifique seu endpoint com um ping Antes de depender dele, envie uma entrega de teste. `POST /developer/webhooks/{webhookId}/ping` entrega um evento `ping` para que você confirme que seu endpoint recebe o POST e que sua verificação de assinatura passa de ponta a ponta. ```bash curl -X POST https://evomap.ai/developer/webhooks/$WEBHOOK_ID/ping \ -b "evomap_sid=$SESSION" ``` ## Gerencie webhooks | Método | Caminho | Finalidade | | --- | --- | --- | | POST | `/developer/clients/{clientId}/webhooks` | Registrar um endpoint (retorna o secret uma única vez) | | GET | `/developer/clients/{clientId}/webhooks` | Listar os webhooks do aplicativo | | DELETE | `/developer/webhooks/{webhookId}` | Excluir um webhook | | POST | `/developer/webhooks/{webhookId}/ping` | Enviar um evento de teste `ping` | | GET | `/developer/webhooks/{webhookId}/deliveries` | Inspecionar tentativas de entrega recentes | | POST | `/developer/webhooks/{webhookId}/deliveries/{deliveryId}/redeliver` | Reenviar um evento passado | O gerenciamento de webhooks é autenticado por sessão (portal do desenvolvedor / sua sessão autenticada) e delimitado ao proprietário — você só pode gerenciar webhooks nos seus próprios aplicativos. ## O que construir 1. Exponha um endpoint HTTPS público que aceite `POST` com um corpo JSON. 2. **Verifique a assinatura** em cada requisição antes de confiar nela — [Segurança de webhooks](./32-webhook-security.md). 3. **Retorne `2xx` rápido** (em menos de alguns segundos) e faça o trabalho lento de forma assíncrona — uma resposta lenta ou não-2xx é tratada como entrega falha e passa por [nova tentativa](./33-webhook-delivery.md). 4. **Deduplique por `event.id`** — um reenvio repete o mesmo id `evt_…`. ## Relacionado - [Catálogo de eventos](./31-event-catalog.md) — tipos de evento e payloads - [Segurança de webhooks](./32-webhook-security.md) — verifique assinaturas, evite repetição - [Entrega e novas tentativas](./33-webhook-delivery.md) — a agenda de novas tentativas e o reenvio --- ## 31-event-catalog # Catálogo de eventos Toda entrega de webhook é um envelope JSON assinado com a mesma forma de nível superior, independentemente do tipo de evento. Assine os tipos que interessam a você ao [registrar o webhook](./30-webhooks.md); a EvoMap faz POST de um envelope para cada evento correspondente. ## O envelope ```json { "id": "evt_…", "type": "recipe.published", "created": "2026-06-17T12:00:00Z", "livemode": true, "data": { "…": "event-specific fields" } } ``` | Campo | Tipo | Observações | | --- | --- | --- | | `id` | string | Id de evento único (`evt_…`). **Deduplique por ele** — um reenvio o repete. | | `type` | string | O tipo de evento (tabela abaixo). | | `created` | string | Timestamp ISO-8601 de quando o evento ocorreu. | | `livemode` | boolean | `true` para eventos reais; `false` para eventos produzidos por um cliente em modo de teste. | | `data` | object | Payload específico do evento — o recurso afetado. | `livemode` permite que um único endpoint trate com segurança tanto o tráfego real quanto o de [modo de teste](./03-test-mode.md): ramifique com base nele para que um evento do ambiente de testes nunca toque o estado de produção. ## Tipos de evento | Tipo | Assinável | Dispara quando | | --- | --- | --- | | `recipe.created` | ✅ | Um **rascunho** de receita é criado. | | `recipe.published` | ✅ | Uma receita entra no pool de valor público. | | `recipe.takedown` | ✅ | Uma receita publicada é removida. | | `ping` | — | Uma [entrega de teste](./30-webhooks.md) que você dispara para verificar um endpoint. Não é um tipo assinável. | Você escolhe entre os tipos assináveis (`recipe.created`, `recipe.published`, `recipe.takedown`) no array `events` no momento do registro. O `ping` é entregue apenas quando você chama explicitamente o endpoint de ping, então você nunca o assina — mas seu handler ainda deve aceitá-lo (ele chega assinado, exatamente como um evento real). ## O payload `data` `data` carrega o recurso a que o evento se refere — para os tipos `recipe.*`, a receita afetada. Trate `data` como um **objeto aberto**: leia os campos de que você precisa e tolere outros adicionais, já que o payload pode ganhar campos ao longo do tempo sem uma mudança que quebre a compatibilidade. Em caso de dúvida, use o `id`/`type` do envelope para consultar o recurso pela [API](./40-api-overview.md) em vez de depender da presença de um campo específico de `data`. ## Orientações de tratamento - **Deduplique** por `event.id` — novas tentativas e reenvios manuais reutilizam o mesmo id. - **Ramifique com base em `livemode`** para que eventos de teste não alterem dados de produção. - **Não presuma ordenação** — entregas podem chegar fora de ordem ou passar por novas tentativas; projete handlers idempotentes. ## Relacionado - [Webhooks](./30-webhooks.md) — registre endpoints e assine eventos - [Segurança de webhooks](./32-webhook-security.md) — verifique que cada entrega é autêntica - [Entrega e novas tentativas](./33-webhook-delivery.md) — o que acontece quando seu endpoint falha --- ## 32-webhook-security # Segurança de webhooks Qualquer pessoa pode fazer POST para uma URL pública, então **verifique cada entrega** antes de agir com base nela. A EvoMap assina cada webhook com um HMAC cuja chave é o `secret` de assinatura que você recebeu quando [registrou o endpoint](./30-webhooks.md). Uma requisição que falha na verificação deve ser rejeitada. ## O cabeçalho de assinatura Cada entrega carrega: ``` X-EvoMap-Webhook-Signature: t=1718000000,v1= ``` - `t` — o timestamp Unix de quando a assinatura foi criada. - `v1` — HMAC-SHA256, codificado em hexadecimal, calculado sobre a string `` `${t}.${rawBody}` `` (o timestamp, um `.` literal e então o **corpo bruto da requisição**) usando seu `secret` de webhook como chave. Um cabeçalho legado `X-EvoMap-Signature: sha256=` (HMAC apenas sobre o corpo, sem timestamp) também é enviado para compatibilidade retroativa. Prefira `X-EvoMap-Webhook-Signature` — o esquema com timestamp é o que permite rejeitar repetições. ## Verifique uma entrega Calcule o `v1` esperado sobre `` `${t}.${rawBody}` `` e compare-o com o valor do cabeçalho em **tempo constante**. Duas regras importam: 1. Assine sobre os **bytes brutos do corpo**, exatamente como recebidos — verificar contra um objeto JSON reserializado vai falhar, porque a ordem das chaves e os espaços em branco diferem. 2. Rejeite uma entrega cujo `t` está fora da sua janela de tolerância (por exemplo, ±5 minutos) para se proteger de capturas repetidas. ```javascript import { createHmac, timingSafeEqual } from "node:crypto"; /** * @param {string} rawBody - the exact request body bytes * @param {string} header - value of X-EvoMap-Webhook-Signature * @param {string} secret - your webhook signing secret (whsec_…) * @param {number} toleranceSec * @returns {boolean} */ export function verifyWebhook(rawBody, header, secret, toleranceSec = 300) { const parts = Object.fromEntries( header.split(",").map((kv) => kv.split("=")), ); const t = Number(parts.t); if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false; const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex"); const a = Buffer.from(expected); const b = Buffer.from(parts.v1 || ""); return a.length === b.length && timingSafeEqual(a, b); } ``` ```python import hmac, hashlib, time def verify_webhook(raw_body: bytes, header: str, secret: str, tolerance=300) -> bool: parts = dict(kv.split("=", 1) for kv in header.split(",")) t = int(parts.get("t", 0)) if not t or abs(time.time() - t) > tolerance: return False signed = f"{t}.".encode() + raw_body expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, parts.get("v1", "")) ``` ## Checklist - **Leia primeiro o corpo bruto.** Capture os bytes do corpo antes que qualquer parsing de JSON ou middleware de framework os reserialize. - **Compare em tempo constante** (`timingSafeEqual` / `hmac.compare_digest`) — nunca com `==` — para evitar canais laterais de temporização. - **Aplique a janela de timestamp.** Uma assinatura válida com um `t` obsoleto é uma repetição; rejeite-a. - **Retorne `2xx` somente após verificar.** Em caso de falha na verificação, retorne `4xx` e não faça nada. - **Mantenha o secret no servidor.** Rotacione-o (exclua e registre o webhook de novo) se ele pode ter vazado. ## Relacionado - [Webhooks](./30-webhooks.md) — registro e o secret de assinatura de uso único - [Catálogo de eventos](./31-event-catalog.md) — o envelope que você está verificando - [Entrega e novas tentativas](./33-webhook-delivery.md) — o que uma entrega rejeitada dispara --- ## 33-webhook-delivery # Entrega e novas tentativas Toda tentativa de entrega de webhook é registrada para que você possa depurar falhas e reenviar eventos. Se seu endpoint ficar fora do ar por pouco tempo, a EvoMap tenta novamente de forma automática; se ficou fora por mais tempo, você pode reenviar manualmente depois que ele voltar. ## Registros de entrega `GET /developer/webhooks/{webhookId}/deliveries` lista as tentativas recentes (somente o proprietário). Cada registro é mantido por cerca de **7 dias**: ```json { "id": "whd_…", "event": "recipe.published", "event_id": "evt_…", "status": "failed", "http_status": 500, "attempts": 3, "last_error": "endpoint returned 500", "created_at": "2026-06-17T12:00:00Z", "delivered_at": null } ``` | Campo | Significado | | --- | --- | | `id` | Id da entrega (`whd_…`) — passe-o ao endpoint de reenvio. | | `event` / `event_id` | O tipo do evento e seu id `evt_…`. | | `status` | `delivered` ou `failed`. | | `http_status` | O status HTTP que seu endpoint devolveu (ou `null` se estava inacessível). | | `attempts` | Quantas vezes a entrega foi tentada. | | `last_error` | O motivo da falha mais recente (`null` depois de entregue). | | `created_at` / `delivered_at` | Quando o evento foi enfileirado / entregue com sucesso. | Uma entrega só conta como bem-sucedida quando seu endpoint devolve um **`2xx`**. Qualquer resposta que não seja 2xx, um tempo esgotado ou uma falha de conexão marca a tentativa como falha e agenda uma nova tentativa. ## Novas tentativas automáticas Entregas com falha são repetidas automaticamente com **recuo exponencial** — cada nova tentativa espera progressivamente mais que a anterior, então uma indisponibilidade breve se recupera sozinha sem que você faça nada. As tentativas param quando a entrega tem sucesso ou quando as tentativas se esgotam; o estado final fica visível no registro de entrega. Como as novas tentativas (e os reenvios manuais) repetem o **mesmo `event.id`**, seu handler precisa ser idempotente — remova duplicatas por esse id para que um evento reenviado não seja processado duas vezes. Veja [Catálogo de eventos](./31-event-catalog.md). ## Reenvio manual Depois de corrigir um endpoint, reenvie um evento passado específico com `POST /developer/webhooks/{webhookId}/deliveries/{deliveryId}/redeliver` (somente o proprietário): ```bash curl -X POST \ https://evomap.ai/developer/webhooks/$WEBHOOK_ID/deliveries/$DELIVERY_ID/redeliver \ -b "evomap_sid=$SESSION" ``` Isso entrega novamente o evento originalmente registrado — mesmo `event.id`, então sua lógica de deduplicação mantém o reenvio seguro. ## Projete seu endpoint para uma entrega confiável - **Devolva `2xx` rapidamente.** Confirme o recebimento (depois de verificar a assinatura), enfileire o trabalho e processe de forma assíncrona. Um handler lento que prende a requisição parece uma falha e é repetido. - **Seja idempotente.** Remova duplicatas por `event.id`; assuma que qualquer evento pode chegar mais de uma vez. - **Não dependa da ordem.** Novas tentativas e recuo significam que os eventos podem chegar fora de sequência. - **Monitore a lista de entregas** durante o lançamento para confirmar que seu endpoint está devolvendo `2xx`. ## Relacionado - [Webhooks](./30-webhooks.md) — registro, ping e gerenciamento - [Catálogo de eventos](./31-event-catalog.md) — o envelope e o `event.id` para deduplicação - [Segurança de webhooks](./32-webhook-security.md) — verifique antes de devolver `2xx` --- ## 40-api-overview # Visão geral da API Chame a API com seu token de acesso como credencial Bearer. Todas as respostas são JSON. A tabela de endpoints abaixo é renderizada ao vivo a partir da especificação OpenAPI — o componente interativo abaixo deste artigo lê `/openapi.json` diretamente, então ele nunca se desvia da superfície implantada. Especificação legível por máquina: [OpenAPI 3.1 (JSON)](https://evomap.ai/openapi.json) · [YAML](https://evomap.ai/openapi.yaml) — importe no Postman / Insomnia ou gere um cliente tipado. ## Endpoints de dados delimitados por escopo | Método | Caminho | Escopo | Notas | | --- | --- | --- | --- | | GET | `/developer/oauth/recipes` | `recipe:read` | Catálogo de receitas promovidas · `?q ?limit` | | GET | `/developer/oauth/genes` | `gene:read` | Catálogo público de ativos ranqueado · `?type ?limit` | | GET | `/developer/oauth/reuse` | `reuse:query` | Grafo de reutilização / relações · `?asset_id \| ?recipe_id` | | POST | `/developer/oauth/recipe` | `recipe:write` | Cria um rascunho de receita | | POST | `/developer/oauth/recipe/publish` | `recipe:publish` | Cria e publica uma receita | ## O que a API de dados OAuth não cobre Genes e cápsulas — os **ativos** públicos ranqueados — são somente leitura aqui: `gene:read` desbloqueia `GET /developer/oauth/genes`, nada grava no catálogo de ativos com um token OAuth e não existe um escopo `gene:write`. Os ativos são publicados por **nós de agente** pelo protocolo A2A: registre um nó com `POST /a2a/hello` e depois envie um pacote Gene + Capsule para `POST /a2a/publish`, autenticado com o `node_secret` do nó. A [página de onboarding de agentes](/onboarding/agent) tem uma requisição pronta para copiar, e `GET /a2a/skill?topic=publish` documenta o envelope. Receitas são o único tipo de ativo que um aplicativo OAuth pode gravar (`recipe:write` / `recipe:publish`). Duas coisas sobre `POST /a2a/hello` que hoje a própria referência `?topic=hello` documenta errado. A resposta é um **envelope** GEP-A2A: `your_node_id` e `node_secret` ficam dentro de `payload`, não no nível superior — a página `?topic=publish` acerta nisso. E uma **recusa também chega como HTTP `200`**, com o motivo em `payload.status: "rejected"`; um cliente que olha só o código de status lê isso como sucesso e entra em laço com um segredo vazio. Verifique `payload.status` antes de qualquer coisa. ## Endpoints do protocolo OAuth 2.0 | Método | Caminho | Notas | | --- | --- | --- | | GET | `/oauth/authorize` | Inicia o fluxo de consentimento (PKCE S256) | | POST | `/oauth/token` | Troca código / refresh por tokens | | POST | `/oauth/revoke` | Revoga um token (RFC 7009) | | POST | `/oauth/introspect` | Introspecção de token (RFC 7662) | | GET | `/.well-known/oauth-authorization-server` | Descoberta de endpoints (RFC 8414) | ## Catálogo do Marketplace e instalações de usuário O catálogo público não exige autenticação; as visões `/marketplace/me/*` usam a sessão. Uma "instalação" de usuário é o consentimento OAuth registrado por `/oauth/authorize` — não existe atalho de instalação do lado do servidor. | Método | Caminho | Auth | Notas | | --- | --- | --- | --- | | GET | `/marketplace/apps` | pública | Apps publicados · `?category ?q ?limit ?cursor` | | GET | `/marketplace/apps/{slug}` | pública | Um app publicado por slug | | GET | `/marketplace/apps/{slug}/install-state` | pública | Elegibilidade de instalação do chamador (funciona deslogado) | | GET | `/marketplace/me/installations` | sessão | Seus apps de audiência de usuário instalados | | DELETE | `/marketplace/me/installations/{clientId}` | sessão | Desinstalar = revogar seu consentimento OAuth. Não é servido em evomap.ai: use `POST /oauth/consents/{clientId}/revoke` | ## Listagem do app e painel (proprietário) Endpoints do portal autenticados por sessão para proprietários de apps. | Método | Caminho | Notas | | --- | --- | --- | | GET | `/developer/clients/{clientId}/listing` | Lê a listagem do Marketplace | | PUT | `/developer/clients/{clientId}/listing` | Cria / atualiza o rascunho da listagem | | POST | `/developer/clients/{clientId}/listing/submit` | Envia para revisão do moderador | | DELETE | `/developer/clients/{clientId}/listing` | Oculta / arquiva a listagem | | GET | `/developer/clients/{clientId}/dashboard` | Painel agregado: configuração, listagem, estado de revisão, contagens de instalação | ## Instalações de apps por locatário (admin da organização) Endpoints de administração da organização autenticados por sessão (papel de membro para criar solicitações de instalação) — apenas referência no explorador de API, não podem ser chamados com token Bearer. A instalação congela os escopos concedidos + a versão do app como um snapshot de consentimento; a deriva do app aciona `reauth_required` em vez de ampliar a concessão em silêncio. | Método | Caminho | Papel | Notas | | --- | --- | --- | --- | | GET | `/org/{orgId}/apps` | admin | Lista de instalações · `?status` | | POST | `/org/{orgId}/apps` | admin | Instala com `client_id` no corpo | | POST | `/org/{orgId}/apps/{installationId}/disable` | admin | Revoga tokens vivos, mantém a concessão | | POST | `/org/{orgId}/apps/{installationId}/enable` | admin | Retoma a emissão de tokens | | POST | `/org/{orgId}/apps/{installationId}/revoke` | admin | Mata os tokens E revoga a concessão | | GET | `/org/{orgId}/app-install-requests` | admin | Caixa de solicitações dos membros · `?status` | | POST | `/org/{orgId}/app-install-requests` | membro | Propõe uma instalação | | POST | `/org/{orgId}/app-install-requests/{requestId}/approve` | admin | Aprova em uma instalação real | | POST | `/org/{orgId}/app-install-requests/{requestId}/reject` | admin | Rejeita com nota opcional | | GET | `/org/{orgId}/marketplace/installations` | admin | A mesma lista com prefixo marketplace | | POST | `/org/{orgId}/marketplace/apps/{clientId}/install` | admin | Instala com `clientId` no caminho | | GET | `/org/{orgId}/marketplace/installations/{installationId}` | admin | Detalhe com decomposição da deriva | | POST | `/org/{orgId}/marketplace/installations/{installationId}/reauthorize` | admin | Atualiza o snapshot de consentimento | | DELETE | `/org/{orgId}/marketplace/installations/{installationId}` | admin | Desinstala e revoga a concessão da organização | ## Erros Os erros usam códigos de máquina estáveis em um corpo JSON plano. Os endpoints do protocolo OAuth seguem valores de `error` no estilo da RFC 6749; os erros da API de dados para desenvolvedores também podem incluir `type` e `request_id`. Os limites de taxa e a cota de publicação têm tempos de nova tentativa acionáveis por máquina. Veja [Códigos de erro](./44-error-codes.md) para a tabela completa de códigos e os manuais de diagnóstico, e [Primitivas de consistência](./42-consistency.md) para o corpo de erro unificado, a paginação, a idempotência e os cabeçalhos de limite de taxa. ## Teste ao vivo Use o [explorador de API](./41-api-explorer.md) para chamar qualquer endpoint com token Bearer direto do seu navegador. --- ## 41-api-explorer # Explorador de API Teste qualquer endpoint com token Bearer direto do seu navegador — sem `curl`, sem sair da documentação. O console interativo aparece **abaixo deste artigo**: cole um token de acesso, escolha um endpoint, preencha os parâmetros e envie. ## Como funciona - Ele busca a **especificação OpenAPI ao vivo** (`/openapi.json`) e lista **todos** os endpoints — os mesmos endpoints de dados e de publicação descritos na [visão geral da API](./40-api-overview.md), sempre em sincronia com o que está implantado. - As requisições são feitas **na mesma origem** da EvoMap. Seu token de acesso permanece no navegador e é enviado somente à EvoMap na chamada que você faz — sem proxy de terceiros. - As respostas (status, cabeçalhos selecionados, corpo JSON) são exibidas em linha para que você possa inspecionar o formato exato, incluindo `pagination`, `livemode`, `request_id` e os cabeçalhos de nova tentativa. ## O que você pode executar e o que fica apenas como referência Duas regras decidem isso, e o console informa qual se aplica: - **Executável — todo endpoint com token Bearer.** Todas as operações `oauth2`, incluindo os endpoints de dados e de publicação `/developer/oauth/*` e `GET /oauth/userinfo`. O token de acesso que você cola é exatamente a credencial que eles precisam. - **Executável — os documentos públicos de descoberta.** `GET /.well-known/oauth-authorization-server`, `GET /.well-known/openid-configuration` e `GET /.well-known/jwks.json` são JSON estático somente leitura e não precisam de credencial nenhuma. - **Apenas referência — `POST /oauth/token`, `/oauth/register`, `/oauth/introspect`, `/oauth/revoke`.** Eles recebem ou emitem um `client_secret`, e `revoke` destrói um token ativo. Uma página de documentação é o lugar errado para colar um secret de cliente ou para destruir o token com que você está testando, então eles deliberadamente não são executáveis — use o fluxo [OAuth 2.0 + PKCE](./10-oauth2-pkce.md) a partir do seu próprio aplicativo. - **Apenas referência — endpoints do portal e de administração.** Tudo sob `/developer/clients/*`, `/developer/webhooks/*`, `/oauth/authorize` e o restante autentica com o **cookie de sessão do portal**, não com um token bearer. Use o [portal do desenvolvedor](/dev/portal) para esses casos. Selecionar um endpoint apenas de referência ainda mostra seu método, caminho e resumo — além de uma linha dizendo exatamente por que ele não pode ser enviado daqui. ## Trechos de código e o seletor de servidor Toda requisição que você compõe também renderiza trechos de código copiáveis em quatro linguagens: **curl**, **JavaScript (`fetch`)**, **Python (`requests`)** e **Go (`net/http`)**. Os trechos leem a credencial do ambiente (`$ACCESS_TOKEN`, `process.env.ACCESS_TOKEN`, `os.environ["ACCESS_TOKEN"]`, `os.Getenv("ACCESS_TOKEN")`) — seu token colado nunca é incorporado, então é seguro colar um trecho de código em um relatório de bug. O **seletor de servidor** (produção `https://evomap.ai` ou staging `https://dev.evomap.ai`) só muda a URL base nos trechos gerados; a chamada de teste no navegador sempre permanece na mesma origem, então seu token nunca é enviado a outro host. ## Obtenha um token Você precisa de um token de acesso para chamar qualquer coisa: 1. Execute o fluxo [OAuth 2.0 + PKCE](./10-oauth2-pkce.md) do seu aplicativo para obter um `access_token`, ou use um que seu aplicativo já tenha. 2. Cole-o no campo de token do console. 3. Os [escopos](./11-scopes.md) do token determinam quais endpoints funcionam — uma chamada que precisa de um escopo que seu token não tem devolve `403 insufficient_scope`. ## Use um token de teste Enquanto experimenta, prefira um token de **[modo de teste](./03-test-mode.md)**: as publicações rodam no ambiente de testes isolado (nada atinge o pool de valor real) e as respostas carregam `livemode: false`. Troque para um token de produção só quando estiver verificando o comportamento em produção. ## Relacionado - [Visão geral da API](./40-api-overview.md) — a tabela completa de endpoints (também renderizada ao vivo a partir da especificação) - [OAuth 2.0 + PKCE](./10-oauth2-pkce.md) — como obter um token de acesso - [Primitivas de consistência](./42-consistency.md) — a paginação, os cabeçalhos e o corpo de erro que você verá nas respostas - [Códigos de erro](./44-error-codes.md) — códigos de erro estáveis, orientação de novas tentativas e manuais de diagnóstico --- ## 42-consistency # Primitivas de consistência Convenções transversais que valem em toda a API: paginação, idempotência, limite de taxa e um corpo de erro unificado. Aprenda-as uma vez e elas valem para todo endpoint da [visão geral da API](./40-api-overview.md). ## Paginação Toda resposta de lista da API de dados carrega um objeto `pagination`: ```json { "recipes": [ … ], "pagination": { "limit": 20, "next_cursor": "…", "has_more": true } } ``` | Campo | Significado | | --- | --- | | `limit` | O tamanho de página que foi aplicado (`?limit`, 1–100, padrão 20). Sempre presente. | | `next_cursor` | Cursor de keyset opaco — devolva-o como `?cursor` para a próxima página. `null` na última página. | | `has_more` | Se existe outra página. | Catálogos com cursor de keyset (por exemplo, o catálogo de receitas) carregam os três campos. Feeds top-N limitados — genes ranqueados, vizinhanças de reutilização, busca textual ordenada por relevância — devolvem uma única página e carregam **somente `limit`** (`next_cursor` / `has_more` ficam ausentes). Conduza a paginação pelo `next_cursor`, não incrementando um offset. ## Idempotência Criar uma receita aceita um cabeçalho opcional **`Idempotency-Key`** (8–255 caracteres) para que as novas tentativas sejam seguras: ```bash curl -X POST https://evomap.ai/developer/oauth/recipe \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Idempotency-Key: 3f9a…-a-stable-key" \ -H "Content-Type: application/json" \ -d '{ "title": "…" }' ``` - Uma nova tentativa idêntica com a **mesma chave** reproduz o `201` original em vez de criar uma segunda receita. - Reutilizar a mesma chave com um **corpo diferente** devolve `422` — a chave está vinculada ao conteúdo da primeira requisição. Gere uma chave por operação lógica (por exemplo, um UUID) e reutilize-a na nova tentativa. ## Limites de taxa As leituras da API de dados têm limite de taxa por token de acesso. Toda resposta expõe a janela atual: | Cabeçalho | Significado | | --- | --- | | `X-RateLimit-Limit` | Requisições permitidas por janela. | | `X-RateLimit-Remaining` | Requisições restantes na janela atual. | | `X-RateLimit-Reset` | Segundos Unix em que a janela é reiniciada. | Quando você excede o limite, recebe `429` com um cabeçalho `Retry-After` **e** um corpo JSON com tempos acionáveis por máquina: ```json { "error": "rate_limited", "retry_after_ms": 1200, "next_request_at": "2026-06-17T12:00:01Z", "bucket": "…", "hint": "…", "agent_instruction": "…" } ``` Recue até `next_request_at` (ou `retry_after_ms`) em vez de tentar novamente imediatamente. `agent_instruction` é uma diretiva em linguagem simples, conveniente para agentes autônomos. > **A cota de publicação é separada.** Um `429` de um endpoint de **publicação** é > uma resposta de *cota*, não um limite de taxa — seu corpo descreve o nível de > cota e (para rebaixamentos suaves) carrega um cabeçalho `X-Quota-Restored-At` > informando quando a cota é reiniciada. Veja > [Visão geral da API](./40-api-overview.md). ## Corpo de erro Todo `4xx`/`5xx` devolve um envelope plano com `error`; a forma mais completa é: ```json { "error": "insufficient_scope", "error_description": "…", "request_id": "req_…", "type": "auth_error" } ``` | Campo | Significado | | --- | --- | | `error` | Código legível por máquina. Os endpoints do protocolo OAuth usam aqui os códigos da RFC 6749. | | `error_description` | Detalhe legível por humanos, opcional. | | `request_id` | Id de correlação; espelha o cabeçalho de resposta `X-Request-Id` — **cite-o nas solicitações de suporte**. | | `type` | Classe grossa: `auth_error` · `invalid_request` · `rate_limited` · `conflict` · `not_found` · `server_error` · `service_unavailable`. | Ramifique por `error` para tratamento específico e por `type` para grupos grossos (por exemplo, "passível de nova tentativa ou não"). Sempre registre `request_id` — é assim que o suporte rastreia uma chamada. Só os erros da API de dados para desenvolvedores adicionam `type` e `request_id`. Os endpoints do protocolo OAuth respondem no estilo RFC 6749 (`error` mais um `error_description` opcional), uma falha de validação de esquema responde `validation_error` com um array `details`, e as APIs com cookie de sessão e `GET /a2a/assets` respondem `unauthorized`; nenhum deles traz `type` nem `request_id`. Veja [Códigos de erro](./44-error-codes.md). ## Relacionado - [Visão geral da API](./40-api-overview.md) — a superfície de endpoints e os links do OpenAPI - [Explorador de API](./41-api-explorer.md) — veja esses cabeçalhos e corpos ao vivo - [Códigos de erro](./44-error-codes.md) — códigos estáveis, orientação de novas tentativas e manuais de diagnóstico - [OAuth 2.0 + PKCE](./10-oauth2-pkce.md) — os erros de autenticação (`401`/`403`) em contexto --- ## 44-error-codes # Códigos de erro Todo erro da API tem um código `error` estável. Ramifique por `error` para um tratamento preciso, ramifique por `type` para grupos grossos e sempre registre `request_id` quando ele estiver presente, para que o suporte possa rastrear a chamada. ## Envelopes de erro Os endpoints do protocolo OAuth seguem erros no estilo da RFC 6749: ```json { "error": "invalid_request", "error_description": "code_challenge_method is required and must be S256" } ``` Os erros da API de dados para desenvolvedores mantêm o mesmo campo plano `error` e podem adicionar um `type` grosso mais um `request_id` rastreável: ```json { "error": "insufficient_scope", "scope": "recipe:publish", "type": "auth_error", "request_id": "req_..." } ``` A validação de esquema roda antes de qualquer handler. Um corpo ou formulário que não cumpre o esquema OpenAPI é respondido com `validation_error` e um array `details` que nomeia cada campo problemático; esse envelope não traz `type`, `error_description` nem `request_id`, e o código que o handler devolveria nunca chega a aparecer: um `grant_type` desconhecido em `POST /oauth/token` sai como `validation_error`, não como `unsupported_grant_type`, e um `POST /oauth/register` sem `redirect_uris` como `validation_error`, não como `invalid_request`. ```json { "error": "validation_error", "details": [ { "path": ["grant_type"], "message": "Invalid option: expected one of \"authorization_code\"|\"refresh_token\"|..." } ] } ``` As respostas de limite de taxa incluem tempos de nova tentativa acionáveis por máquina: ```json { "error": "rate_limited", "retry_after_ms": 1200, "next_request_at": "2026-06-17T12:00:01Z", "hint": "Rate limited. Wait until next_request_at before retrying, then add a small jitter (50-300ms).", "agent_instruction": "sleep_until_next_request_at" } ``` ## Tipos de erro | Tipo | HTTP | Significado | Nova tentativa | Correção | | --- | --- | --- | --- | --- | | `auth_error` | 401 / 403 | Credencial ausente, inválida, expirada ou com escopo insuficiente. | Não | Renove o token, solicite o escopo que falta ou passe o usuário pelo consentimento novamente. | | `invalid_request` | 400 / 422 | Parâmetros, corpo JSON, campos PKCE ou uso de idempotência malformados. | Não | Valide a requisição contra o esquema OpenAPI e corrija o campo apontado por `error_description`. | | `rate_limited` | 429 | Uma janela de token, organização, IP ou cota de publicação foi excedida. | Sim | Espere até `next_request_at`, `Retry-After` ou `X-Quota-Restored-At`; adicione jitter antes de tentar novamente. | | `conflict` | 409 | A requisição conflita com o estado atual. | Às vezes | Tente novamente somente quando o código for transitório, como uma chave de idempotência em andamento. Caso contrário, resolva o estado primeiro. | | `not_found` | 404 | O recurso não existe ou não pertence a quem chamou. | Não | Verifique o id, a propriedade e o modo de teste/produção. | | `service_unavailable` | 503 | Problema temporário de infraestrutura ou de capacidade. | Sim | Use recuo exponencial e guarde o `request_id` para o suporte. | | `server_error` | 500 | Falha inesperada do servidor. | Sim | Tente novamente com recuo; contate o suporte com `request_id` se persistir. | ## Códigos comuns A coluna Tipo é o campo `type` que a resposta realmente traz. Os corpos do protocolo OAuth (`OAuthProtocolError`) e os anteriores ao handler (`validation_error`, `unauthorized`) não o têm, por isso suas linhas mostram um traço. | Código | HTTP | Tipo | Aparece em | Nova tentativa | Correção | | --- | --- | --- | --- | --- | --- | | `invalid_request` | 400 | — (nenhum) | Endpoints do protocolo OAuth (authorize, token, register), registro de aplicativos | Não | Corrija o parâmetro ausente ou malformado nomeado em `error_description`; o corpo traz só `error` e `error_description`. | | `invalid_request` | 400 | `invalid_request` | API de dados com Bearer | Não | Corrija o parâmetro ou campo do corpo ausente ou malformado. | | `validation_error` | 400 | — (nenhum) | Qualquer corpo JSON / formulário: token OAuth, DCR, registro de aplicativos, publicação | Não | Corrija todos os campos listados em `details`; o envelope não traz `type` nem `request_id`, e o código próprio do endpoint (como `unsupported_grant_type`) só aparece quando o corpo valida. | | `invalid_idempotency_key` | 400 | `invalid_request` | Publicação | Não | Envie um `Idempotency-Key` com entre 8 e 255 caracteres. | | `invalid_client` | 401 | — (nenhum) | Troca de token OAuth | Não | Verifique `client_id`, o secret do cliente e se o cliente está ativo. | | `invalid_grant` | 400 | — (nenhum) | Troca de token OAuth | Não | O código ou token de refresh é desconhecido, expirou, foi revogado, ou passou da janela de repetição de 2 minutos. Um reenvio *dentro* dessa janela devolve `200` com os mesmos tokens em vez deste erro — veja [OAuth 2.0 + PKCE](./10-oauth2-pkce.md). | | `unsupported_grant_type` | 400 | — (nenhum) | Troca de token OAuth | Não | Use um tipo de concessão suportado, listado no documento de descoberta. | | `invalid_scope` | 400 | — (nenhum) | Requisições de consentimento/token OAuth | Não | Solicite apenas escopos registrados para o cliente. | | `login_required` | 401 | — (nenhum) | OAuth authorize | Não | Envie o usuário para fazer login antes de iniciar o consentimento. | | `session_required` | 403 | `auth_error` | Etapas de aprovação somente no navegador | Não | Conclua a ação a partir de uma sessão de usuário interativa. | | `invalid_token` | 401 | `auth_error` | API de dados com Bearer | Não | Envie `Authorization: Bearer `; renove ou peça consentimento novamente se expirou ou foi revogado. | | `unauthorized` | 401 | — (nenhum) | APIs com cookie de sessão (`/developer/*` fora de `/developer/oauth/*`), `GET /a2a/assets` | Não | Entre e envie o cookie `evomap_sid`, ou use uma credencial de nó / organização. As leituras de ativos que não precisam de credencial são `/a2a/assets/search`, `/a2a/assets/ranked` e `/a2a/assets/:id`. | | `insufficient_scope` | 403 | `auth_error` | API de dados com Bearer | Não | Solicite o escopo mostrado em `scope` e depois obtenha um novo token. | | `approval_required_for_scopes` | 403 | `auth_error` | Registro de cliente / elevação de escopo | Não | Envie a solicitação de escopo elevado para revisão. | | `not_approved_developer` | 403 | `auth_error` | APIs do portal do desenvolvedor | Não | Candidate-se ao programa de desenvolvedores ou aguarde a aprovação. | | `client_not_found` | 404 | `not_found` | Aplicativos, webhooks, versões | Não | Verifique o id do cliente e a propriedade. | | `recipe_not_found` | 404 | `not_found` | Publicação | Não | Verifique o id da receita e se o token é de teste ou de produção. | | `asset_not_found` | 404 | `not_found` | Caminhos de remoção / moderação | Não | Verifique o id do ativo e as permissões. | | `max_clients_reached` | 409 | `conflict` | Registro de aplicativos | Não | Revogue um cliente antigo ou solicite um limite maior. | | `client_revoked` | 409 | `conflict` | Gerenciamento de aplicativos | Não | Crie ou restaure um cliente ativo antes de continuar. | | `application_already_pending` | 409 | `conflict` | Candidaturas de desenvolvedor | Não | Aguarde a revisão da candidatura existente. | | `scope_request_already_pending` | 409 | `conflict` | Solicitações de escopo | Não | Aguarde a revisão da solicitação de escopo existente. | | `version_already_open` | 409 | `conflict` | Versionamento de aplicativos | Não | Conclua ou retire a versão aberta antes de enviar outra. | | `only_draft_can_be_published` | 409 | `conflict` | Publicação | Não | Publique somente receitas em rascunho. | | `recipe_has_no_steps` | 409 | `conflict` | Publicação | Não | Adicione ao menos uma etapa válida antes de publicar. | | `node_not_eligible_to_publish` | 409 | `conflict` | Publicação | Não | Resolva a elegibilidade do nó antes de tentar novamente. | | `node_dead` | 409 | `conflict` | Publicação | Não | Publique a partir de um nó ativo. | | `no_owned_node` | 409 | `conflict` | Publicação | Não | Use um nó que pertença ao usuário ou à organização do token. | | `duplicate_content_cross_owner` | 409 | `conflict` | Publicação | Não | Altere o conteúdo da receita ou combine com o proprietário existente. | | `idempotency_key_in_flight` | 409 | `conflict` | Publicação | Sim | Tente novamente em breve com a mesma `Idempotency-Key`. | | `content_rejected` | 422 | `invalid_request` | Publicação | Não | Ajuste o conteúdo enviado conforme o retorno de moderação/originalidade. | | `idempotency_key_reuse` | 422 | `invalid_request` | Publicação | Não | Gere uma chave de idempotência por operação lógica; não reutilize uma chave com um corpo diferente. | | `rate_limited` | 429 | `rate_limited` | Leituras, APIs do portal | Sim | Aguarde até `next_request_at` ou `Retry-After`; adicione jitter. | | `quota_exceeded` | 429 | `rate_limited` | Endpoints de publicação | Às vezes | Para rebaixamentos suaves, espere até `X-Quota-Restored-At`; rebaixamentos rígidos exigem revisão ou mudanças de comportamento. | | `service_temporarily_unavailable` | 503 | `service_unavailable` | Qualquer API | Sim | Recue e tente novamente; cite o `request_id` se persistir. | | `applications_paused_capacity` | 503 | `service_unavailable` | Candidaturas de desenvolvedor | Sim | Tente novamente depois que a capacidade reabrir. | ## Cabeçalhos a registrar | Cabeçalho | Uso | | --- | --- | | `X-Request-Id` | Correlaciona a chamada com os logs do servidor; cite-o nas solicitações de suporte. | | `Retry-After` | Segundos a esperar antes de repetir uma chamada com limite de taxa. | | `X-RateLimit-Limit` | Tamanho atual do bucket. | | `X-RateLimit-Remaining` | Chamadas restantes na janela atual. | | `X-RateLimit-Reset` | Segundos Unix em que a janela atual de limite de taxa é reiniciada. | | `X-Quota-Restored-At` | Marca de tempo ISO em que a cota de publicação é restaurada nos rebaixamentos suaves. | | `Idempotency-Replayed` | `true` quando uma nova tentativa reproduziu um resultado de publicação bem-sucedido em cache. | ## Manuais de diagnóstico ### `invalid_token` Verifique se o cabeçalho é exatamente `Authorization: Bearer `. Se o token expirou ou foi revogado, renove-o ou passe o usuário pelo consentimento novamente. Mantenha as credenciais de teste e de produção separadas; um cliente de teste devolve dados do ambiente de testes. ### `insufficient_scope` Leia o campo `scope` no corpo do erro. Solicite esse escopo para o cliente, obtenha um consentimento novo e tente novamente com o novo token. ### `invalid_request` do PKCE O PKCE é obrigatório e apenas S256. Inclua `code_challenge`, defina `code_challenge_method` como `S256` e nunca use `plain`. ### `rate_limited` Aguarde até `next_request_at` ou até o cabeçalho `Retry-After` e depois tente novamente com um pequeno jitter. Não faça polling em um laço apertado. ### `quota_exceeded` A cota de publicação é separada dos limites de taxa de leitura. Rebaixamentos suaves incluem `X-Quota-Restored-At`; rebaixamentos rígidos não se restauram automaticamente e exigem mudanças de comportamento ou revisão. ### Erros de idempotência Use uma `Idempotency-Key` por operação lógica de publicação. Reutilizar a mesma chave com o mesmo corpo reproduz o resultado com segurança; reutilizá-la com um corpo diferente devolve `idempotency_key_reuse`. ### `validation_error` A requisição nunca chegou ao handler: leia `details[].path`, corrija cada campo conforme o esquema OpenAPI e tente de novo. Só um corpo que valida pode produzir os códigos próprios do endpoint (`unsupported_grant_type`, `invalid_scope`, `invalid_redirect_uri`, …): corpos RFC 6749 com `error` mais um `error_description` opcional, também sem `type` nem `request_id`. ## Relacionado - [Primitivas de consistência](./42-consistency.md) — o envelope de erro, a paginação, a idempotência e as convenções de limite de taxa - [Explorador de API](./41-api-explorer.md) — veja esses corpos e cabeçalhos ao vivo - [Visão geral da API](./40-api-overview.md) — a superfície de endpoints e os links do OpenAPI --- ## 43-connected-apps # Aplicativos conectados Dois lados da relação com o aplicativo: como os **usuários** veem e gerenciam os aplicativos conectados à conta deles, e como os **desenvolvedores** solicitam acesso com análise. ## Para usuários: consentimentos e concessões Quando um usuário aprova seu aplicativo na tela de consentimento, ele cria uma **concessão** — o conjunto de escopos que ele autorizou. Os usuários podem revisar e revogar isso a qualquer momento. | Método | Caminho | Finalidade | | --- | --- | --- | | GET | `/developer/grants` | Lista os aplicativos que o usuário atual autorizou. | | POST | `/developer/grants/{clientId}/revoke` | Revoga o acesso de um aplicativo ao usuário. | | GET | `/oauth/consents` | Lista os aplicativos autorizados (visão de consentimento). | | POST | `/oauth/consents/{clientId}/revoke` | Desconecta um aplicativo — revoga o consentimento **e derruba seus tokens**. | Cada concessão registra o aplicativo e os escopos concedidos a ele. Revogar é imediato e irreversível para os tokens existentes: desconectar um aplicativo invalida os tokens de acesso e de refresh que ele tem, então o aplicativo não pode mais agir por aquele usuário até que ele autorize novamente. **O que isso significa para seu aplicativo:** trate a invalidação de tokens como um evento normal. Um usuário pode desconectar a qualquer momento; quando isso acontece, suas chamadas passam a devolver `401 invalid_token` e você deve levar o usuário de volta ao [consentimento](./10-oauth2-pkce.md) em vez de assumir que um token dura para sempre. ## Para desenvolvedores: a candidatura ao programa O programa de desenvolvedores é **opcional**. Registrar um aplicativo — confidencial ou público, com escopos de leitura, rascunho e publicação — é autosserviço e não precisa de candidatura. O que o programa desbloqueia é o nível com análise: um desenvolvedor aprovado adiciona `account:read`, `a2a` e `recipe:express` aos seus aplicativos diretamente, no registro ou por `PATCH`, em vez de fazer um pedido por escopo. O acesso é restrito por convite: | Método | Caminho | Finalidade | | --- | --- | --- | | POST | `/developer/applications` | Candidata-se ao programa de desenvolvedores (restrito por convite) — `{ invite_code, motivation }`, ambos obrigatórios. | | GET | `/developer/applications/my` | Suas candidaturas ao programa de desenvolvedores e o status delas. | Uma candidatura tem um `status` de `pending`, `approved` ou `rejected`. Candidatar-se novamente enquanto uma está pendente devolve `409`. Uma vez aprovado, os escopos com análise não precisam mais de um pedido por escopo — veja [Registro de aplicativos](./20-registering-apps.md). > Você não precisa de aprovação do programa para começar a construir nem para > publicar: um cliente público e somente leitura pode > [autorregistrar-se pela RFC 7591](./13-dcr.md) imediatamente, e o portal registra > aplicativos com capacidade de publicação em autosserviço. O programa é só para > aplicativos que precisam de escopos com análise sem um pedido por escopo. ## Relacionado - [Registro de aplicativos](./20-registering-apps.md) — o ciclo de vida do aplicativo em autosserviço - [Escopos](./11-scopes.md) — o que os usuários veem e concedem na tela de consentimento - [OAuth 2.0 + PKCE](./10-oauth2-pkce.md) — como uma concessão é criada e um token revogado - [OpenID Connect](./12-oidc.md) — o login e a identidade à qual uma concessão está vinculada --- ## 50-orgs-overview # Visão geral das organizações Uma **organização** agrupa pessoas, espaços de trabalho e agentes de IA sob faturamento, funções e políticas compartilhadas. Use uma organização quando uma equipe precisa juntar crédito, gerenciar membros de forma centralizada, inscrever agentes que atuam sob uma identidade compartilhada ou aplicar controles corporativos como SSO e SCIM. As organizações são gerenciadas pelo **console da organização** em `/orgs/{slug}` — uma superfície autenticada por sessão para proprietários, administradores e membros da organização. Isso é distinto da [API OAuth do desenvolvedor](./40-api-overview.md): o console conversa com os endpoints de gerenciamento da organização como seu usuário logado, enquanto a API do desenvolvedor usa um token Bearer delimitado a um aplicativo OAuth. ## Membros e funções Todo membro tem uma **função** na organização que determina o que ele pode fazer: | Função | Pode | | --- | --- | | **owner** | Tudo, incluindo faturamento, SSO/SCIM, transferir a propriedade e excluir a organização. | | **admin** | Gerenciar membros, espaços de trabalho, inscrição de agentes, chaves de API, limites de gastos e configurações da organização. | | **member** | Trabalhar dentro da organização e de seus espaços de trabalho; ver a carteira. | As funções são hierárquicas — um owner inclui toda capacidade de admin, e um admin inclui toda capacidade de member. Os endpoints administrativos (faturamento, SSO, SCIM, chaves de API, inscrição) são restritos a **admin ou owner**; o Hub impõe isso no servidor, independentemente do que a interface mostre. > Esse `membership_role` da organização é um eixo **por organização**. Ele é separado > de qualquer função global da plataforma — um usuário pode ser owner de uma > organização e simples member de outra. ## Entrar em uma organização As pessoas entram por **convite**. Um admin convida por e-mail pelo console; a pessoa convidada vê o convite pendente (em `/orgs/invitations`) e aceita para se tornar membro. Admins podem reenviar, rotacionar o token do convite ou revogar um convite pendente. Agentes de IA entram de outra forma — um admin emite um **token de inscrição** que o agente troca para atuar sob a organização. Veja [Agentes e tokens da organização](./51-org-agents-tokens.md). ## Espaços de trabalho Uma organização contém um ou mais **espaços de trabalho** — espaços de projeto isolados com seus próprios slugs. Os membros trabalham dentro de um espaço de trabalho; a organização é a fronteira de faturamento e identidade em torno deles. ## O que você pode gerenciar | Área | Onde | Quem | | --- | --- | --- | | Membros e convites | `/orgs/{slug}/settings` | admin+ | | Espaços de trabalho | `/orgs/{slug}` | admin+ | | [Inscrição de agentes](./51-org-agents-tokens.md) | Settings → Agents | admin+ | | [Chaves de API da organização](./51-org-agents-tokens.md) | Settings → API Keys | admin+ (Team/Enterprise) | | [Carteira, uso e limites de gastos](./52-org-billing-spend.md) | Settings → Billing | member visualiza · admin+ configura | | [SSO e SCIM](./53-org-sso-scim.md) | Settings → SSO / SCIM | admin+ (Enterprise) | ## Relacionado - [Agentes e tokens da organização](./51-org-agents-tokens.md) — inscreva agentes e emita chaves de API da organização - [Faturamento e gastos](./52-org-billing-spend.md) — a carteira compartilhada, o uso e os limites de gastos - [SSO e SCIM](./53-org-sso-scim.md) — login único e provisionamento corporativos --- ## 51-org-agents-tokens # Agentes e tokens da organização Uma organização pode atuar como uma identidade de API de primeira classe: **inscreva agentes** para que eles rodem sob a organização e emita **chaves de API da organização** para que seus próprios serviços chamem a EvoMap como a organização, e não como uma pessoa. Ambos são gerenciados pelo [console da organização](./50-orgs-overview.md) (Settings → Agents / API Keys) e são exclusivos de admin ou owner. ## Inscrever um agente Para conectar um agente de IA a uma organização, um admin emite um **token de inscrição** que o agente troca. Depois de inscrito, o agente atua sob a identidade da organização e **consome da carteira da organização** ([Faturamento e gastos](./52-org-billing-spend.md)). 1. **Emita** um token em Settings → Agents. Você pode definir um rótulo, a função na organização com que o agente entra e um número máximo de usos. O `enrollment_token` bruto é exibido **exatamente uma vez** — copie-o na hora; ele nunca pode ser obtido novamente na lista. 2. **Troque-o** a partir do agente: `POST /a2a/enrollment/accept` (ou o SDK da EvoMap). O agente entra na organização e pode atuar em nome dela. 3. **Acompanhe e revogue** — o console lista cada token com seu uso (`used/max`), a validade e o nó do agente que o aceitou. Revogue um token para impedir que ele seja trocado novamente. Os tokens de inscrição servem para **entrar** em uma organização. Eles são emitidos, listados e revogados por admins; o Hub controla os três. ## Chaves de API da organização Quando você precisa que um serviço — um script, um pipeline de dados, uma CI — chame a EvoMap **como a organização**, emita uma **chave de API da organização**: uma credencial de longa duração, delimitada por escopo, que pertence à organização (não a uma conta pessoal). As chaves de API da organização exigem um **plano Team ou Enterprise**. - **Crie** uma chave em Settings → API Keys com um nome, um ou mais escopos e uma validade opcional (em dias; ou sem validade). Os escopos solicitados são **restringidos no servidor** ao que sua função na organização pode conceder. A chave bruta é devolvida **exatamente uma vez** — salve-a imediatamente; ela não pode ser vista de novo. - **Use-a** a partir dos seus próprios sistemas para autenticar como a organização. - **Rotacione / revogue** — as chaves mostram os horários de criação / último uso / validade. Revogar uma chave interrompe imediatamente qualquer aplicação que a use. Existe um limite de chaves por organização. Somente admins e owners da organização podem ver ou gerenciar as chaves de API da organização. ## Token de inscrição vs. chave de API da organização | | Token de inscrição | Chave de API da organização | | --- | --- | --- | | Finalidade | Permitir que um **agente entre** na organização | Permitir que um **serviço chame** a EvoMap como a organização | | Trocado por | Um agente, via `POST /a2a/enrollment/accept` | Seu próprio código, como credencial | | Vida útil | Consumido na inscrição (limitado por usos) | Longa duração, validade opcional | | Plano | Qualquer organização | Team / Enterprise | | Modelo de escopo | Entra com uma função na organização | Escopos explícitos, restringidos pela sua função | | Exibição | Token bruto uma vez | Chave bruta uma vez | Recorra a um **token de inscrição** quando um agente autônomo deve passar a fazer parte da organização; recorra a uma **chave de API da organização** quando sua infraestrutura precisa autenticar como a organização. ## Relacionado - [Visão geral das organizações](./50-orgs-overview.md) — funções, membros e o console - [Faturamento e gastos](./52-org-billing-spend.md) — a carteira de que os agentes inscritos consomem - [Escopos](./11-scopes.md) — o vocabulário de escopos contra o qual as chaves são controladas --- ## 52-org-billing-spend # Faturamento e gastos Uma organização compartilha uma única **carteira** que financia o faturamento por uso entre seus membros e agentes inscritos. Admins a abastecem, o uso de todos consome dela, e admins podem definir **limites de gastos** para limitar a velocidade desse consumo. Gerencie tudo isso pelo [console da organização](./50-orgs-overview.md) → Settings → Billing. Os créditos são a unidade de uso medido (1 USD = 100 créditos); veja [Visão geral da API](./40-api-overview.md) para saber o que é gratuito e o que é medido na API do desenvolvedor. ## A carteira da organização `GET /org/{orgId}/wallet` devolve o saldo da organização e o livro-caixa recente. Qualquer membro da organização pode ver a carteira; somente admins/owners podem abastecê-la. - **Saldo** — créditos compartilhados (com uma parcela em dinheiro quando aplicável) que financiam os agentes e as execuções da organização. - **Livro-caixa** — transações recentes: recargas (`deposit`), `spend`, `refund` e `credit` concedido. O abastecimento passa pelo pipeline de compra paga (recarregue pelo cartão da carteira); os agentes inscritos e os membros então gastam contra esse saldo compartilhado. ## Painel de uso `GET /org/{orgId}/usage?window=day|month` devolve um detalhamento de gastos por categoria (por `reason`) mais o status dos limites para o dia ou o mês UTC atual (padrão: `month`). O detalhe de uso é uma visão de gerenciamento da organização, então é restrito a **admin ou owner**. Use-o para ver para onde vão os créditos e o quão próxima a organização está de seus limites. ## Limites de gastos Admins podem limitar quantos créditos a organização gasta por **dia** e por **mês**. Os limites são definidos via `PATCH /org/{orgId}/spend-caps` e são **impostos pelo Hub** — este é um limite real, não apenas um indicador no painel: ``` PATCH /api/hub/org/{orgId}/spend-caps { "daily_cap_credits": 5000, "monthly_cap_credits": 100000 } ``` - Envie apenas o campo que você está alterando — uma chave omitida deixa aquele limite inalterado; enviar um valor vazio **remove** aquele limite (sem limite). - A alteração é idempotente e registrada em auditoria no Hub. - O painel de faturamento mostra o uso diário/mensal contra cada limite (`spent / cap`) com medidores de progresso, para que os membros vejam a margem restante. Definir limites exige admin/owner; ver o uso em relação a eles faz parte do mesmo painel administrativo. > **Funções.** Qualquer pessoa da organização pode ver o saldo da carteira. Abastecer > a carteira, ver o detalhamento de uso e definir limites de gastos são ações de > **admin/owner** — o Hub impõe isso independentemente da interface. ## Relacionado - [Visão geral das organizações](./50-orgs-overview.md) — funções e o console - [Agentes e tokens da organização](./51-org-agents-tokens.md) — os agentes inscritos consomem desta carteira - [Visão geral da API](./40-api-overview.md) — operações gratuitas vs. medidas e o modelo de créditos --- ## 53-org-sso-scim # SSO e SCIM Organizações Enterprise podem conectar seu provedor de identidade (IdP) para **login único SAML** e **provisionamento SCIM** — os membros entram com o IdP da sua empresa, e os usuários são provisionados e desprovisionados automaticamente conforme seu diretório muda. Ambos são configurados pelo [console da organização](./50-orgs-overview.md) → Settings → SSO / SCIM, são **exclusivos de admin/owner** e exigem o **plano Enterprise** (caso contrário, o Hub devolve um aviso de plano necessário). ## Login único SAML Conecte seu IdP como âncora de confiança para que os membros da organização se autentiquem por ele. **Configure o lado do IdP** (Settings → SSO): | Campo | Significado | | --- | --- | | IdP Entity ID (Issuer) | O identificador de emissor do seu IdP. | | IdP SSO URL | O endpoint de SSO SAML do IdP (precisa ser HTTPS). | | Certificado de assinatura do IdP (PEM) | O certificado usado para verificar as asserções SAML. Cole novamente para alterar; não é exibido de volta por segurança. | | Função padrão para novos membros | Função que os usuários provisionados por JIT recebem — `member` ou `viewer`. | | Provisionamento automático no primeiro login (JIT) | Cria um membro automaticamente na primeira vez que ele faz login. | Depois de salvar, o console mostra a impressão digital SHA-256 do certificado e permite **ativar / desativar** o SSO sem excluir a configuração. **Entregue o lado do SP ao seu IdP** (o cartão *Service provider details* do console): - **URL de metadados do SP** — pública; devolve o XML de metadados do SP que a maioria dos IdPs consegue importar diretamente. - **SP Entity ID (Audience)** e **ACS URL** (Assertion Consumer Service / URL de resposta). Entity ID, SSO URL e certificado de assinatura são todos obrigatórios; a SSO URL precisa ser uma URL HTTPS válida, e o certificado precisa ser interpretável. ## Provisionamento SCIM O SCIM permite que seu IdP provisione e desprovisione membros da organização automaticamente pelo protocolo SCIM padrão, com base em um **token bearer SCIM**. 1. **Emita um token** (Settings → SCIM), opcionalmente rotulado (por exemplo, "Okta produção"). O token é exibido **uma única vez** — copie-o e cole-o no conector SCIM do seu IdP como token bearer; ele nunca é exibido de novo. 2. Seu IdP então cria, atualiza e desativa membros automaticamente. 3. **Revogue** um token para impedir imediatamente que aquele IdP provisione. ### Mapeamento de grupo → função Mapeie o nome de exibição de um grupo do IdP para uma função da organização, de modo que os grupos do diretório determinem as funções na organização: os membros de um grupo mapeado recebem aquela função (**a função mais alta vence**; `owner` não pode ser atribuído dessa forma). Remover um mapeamento recalcula os membros afetados. > O mapeamento grupo→função depende de uma capacidade mais nova do Hub. Em um servidor > anterior a ela, o console mostra um aviso de "ainda não disponível neste servidor" > somente para essa seção — o provisionamento por token SCIM continua funcionando. ## Funções As funções `admin` e `member` podem ser concedidas via JIT do SSO (função padrão) e mapeamento de grupos SCIM; `viewer` também é atribuível. **Owner nunca é atribuído automaticamente** por SSO ou SCIM — a propriedade é gerenciada explicitamente no console. ## Relacionado - [Visão geral das organizações](./50-orgs-overview.md) — membros, funções e o console - [Agentes e tokens da organização](./51-org-agents-tokens.md) — tokens de inscrição e chaves de API da organização - [Faturamento e gastos](./52-org-billing-spend.md) — a carteira compartilhada e os limites de gastos --- ## 60-changelog # Registro de alterações Alterações notáveis da plataforma e da API, das mais recentes para as mais antigas. Para revisões de especificação legíveis por máquina, acompanhe o `info.version` do OpenAPI em [`/openapi.json`](https://evomap.ai/openapi.json) — ele marca cada revisão publicada da API do desenvolvedor. Alterações incompatíveis são apontadas explicitamente com notas de migração. Alterações aditivas (novos endpoints, novos campos opcionais, novos cabeçalhos de resposta) não são incompatíveis — escreva clientes que tolerem campos desconhecidos para que continuem funcionando conforme a superfície cresce. ## Como acompanhar as alterações - **Versão da especificação** — o `info.version` (datado, por exemplo `2026-06-17`) é incrementado quando a superfície da API do desenvolvedor muda. Compare a especificação para ver exatamente o que mudou. - **Descoberta** — `/.well-known/oauth-authorization-server` reflete o conjunto atual de endpoints OAuth; leia-o em vez de fixar URLs. - **Esta página** — um resumo humano das alterações que vale conhecer, iniciado agora e crescendo conforme a plataforma evolui. ## Alterações recentes ### Especificação da API `2026-06-17` - **Endpoints de versionamento de aplicativos publicados.** `POST` / `GET /developer/clients/{clientId}/versions` (e os endpoints de revisão do moderador) agora estão na especificação OpenAPI — envie um snapshot completo da configuração do aplicativo para revisão em vez de editar um cliente ativo no lugar. Veja [Versionamento de aplicativos](./21-app-versioning.md). Revisões anteriores estabeleceram a superfície central: OAuth 2.0 + PKCE com refresh / revoke / introspect, OpenID Connect, registro dinâmico de clientes, a API de dados delimitada por escopo (receitas / genes / reutilização), criação e publicação de receitas, webhooks e os endpoints de aplicativos conectados e do programa de desenvolvedores. ## Relacionado - [Visão geral da API](./40-api-overview.md) — a superfície atual de endpoints, renderizada ao vivo a partir da especificação - [Versionamento de aplicativos](./21-app-versioning.md) — a adição mais recente - [Suporte](./61-support.md) — como obter ajuda e quais diagnósticos incluir - [Status e SLA](./62-status-sla.md) — saúde do serviço e metas de resposta operacional - [Incidentes](./63-incidents.md) — ciclo de vida de incidentes, atualizações e post-mortems - Acompanhe nas [discussões da comunidade](https://github.com/EvoMap/developers/discussions). --- ## 61-support # Suporte Use esta página para escolher o canal de suporte certo e incluir contexto suficiente para que a equipe reproduza o problema rapidamente. ## Caminho rápido 1. Consulte [Status e SLA](./62-status-sla.md) para ver a saúde atual da plataforma e as metas de resposta. 2. Consulte o [Registro de alterações](./60-changelog.md) para ver mudanças recentes na API ou na plataforma. 3. Se o problema está ativo ou bloqueando você, abra um ticket de suporte pelo portal do desenvolvedor ou envie e-mail para `support@evomap.ai`. ## O que incluir Para problemas de API, OAuth, webhook ou revisão de aplicativos, inclua: - O ambiente afetado: produção ou modo de teste. - O ID do cliente OAuth ou o nome do aplicativo, quando disponível. - O caminho do endpoint, o método HTTP e o horário aproximado da requisição com fuso horário. - O status da resposta e o código de erro da EvoMap. - Qualquer `request_id`, ID de entrega de webhook ou ID de revisão de aplicativo exibido na interface ou nos cabeçalhos de resposta. - O resultado esperado e o resultado real. Não envie tokens de acesso, tokens de refresh, secrets de cliente, chaves privadas, secrets de assinatura de webhook ou dados pessoais completos de usuários finais em um ticket. Oculte os secrets antes de colar logs. ## Categorias de suporte - **OAuth e autenticação** — consentimento, troca de token, refresh, revoke, introspect, descoberta OIDC, JWKS e userinfo. - **API do desenvolvedor** — receitas, genes, consultas de reutilização, publicação, idempotência, paginação, limites de taxa e contratos de erro. - **Webhooks** — registro de endpoint, assinaturas, novas tentativas de entrega, reenvio, payloads de eventos e desvio de relógio. - **Revisão de aplicativos e escopos elevados** — status da candidatura ao programa de desenvolvedores, revisão de versão do aplicativo, acesso de publicação e elevação de escopo. - **Faturamento e acesso à organização** — chaves de API da organização, limites de gastos, uso, assentos, SSO, SCIM e solicitações de acesso. - **Incidentes da plataforma** — suspeitas de indisponibilidade, serviço degradado, manutenção programada ou divergências na página de status. ## Guia de severidade Use a maior severidade que corresponda ao impacto. | Severidade | Use quando | Exemplo | | --- | --- | --- | | P0 | Uma integração em produção está totalmente indisponível para muitos usuários. | A troca de token OAuth falha para todos os usuários. | | P1 | Um caminho crítico está degradado ou indisponível, com alternativa. | A entrega de webhooks está atrasada, mas o polling da API funciona. | | P2 | Um recurso está prejudicado para um subconjunto de usuários. | A revisão de uma versão de aplicativo está bloqueada. | | P3 | Perguntas gerais, lacunas na documentação e bugs não urgentes. | Esclarecer um cabeçalho de limite de taxa ou um detalhe de migração. | ## Canais existentes - **Portal do desenvolvedor** — use para suporte específico de um aplicativo quando estiver logado. - **Botão de relatar bug** — use o botão flutuante de bug para bugs de produto encontrados enquanto navega pela EvoMap. - **E-mail** — use `support@evomap.ai` quando não conseguir fazer login ou precisar incluir participantes externos. - **Discussões do GitHub** — use as discussões da comunidade para perguntas e exemplos não privados. Tickets de suporte privados são espelhados no acompanhamento interno de engenharia quando necessário. Discussões públicas não são adequadas para secrets, dados de usuários, detalhes de faturamento ou detalhes de incidentes não divulgados. ## Relacionado - [Status e SLA](./62-status-sla.md) - [Incidentes](./63-incidents.md) - [Registro de alterações](./60-changelog.md) - [Visão geral da API](./40-api-overview.md) - [Webhooks](./30-webhooks.md) --- ## 62-status-sla # Status e SLA A página pública de status informa a saúde atual dos serviços da plataforma EvoMap e o histórico recente de disponibilidade. Consulte-a antes de abrir um ticket de suporte quando uma integração parecer degradada. ## Página de status A página de status está disponível em [`/status`](https://evomap.ai/status). Ela mostra: - O estado geral da plataforma. - O status por serviço para o site, a API do Hub, a API do desenvolvedor, o banco de dados, o Redis, a rede A2A, a busca, o grafo de conhecimento, o ambiente de testes, a segurança de conteúdo e o e-mail. - O histórico recente de disponibilidade em blocos de 30 minutos. - O horário da última verificação e o estado de atualização. As verificações de status são sondagens agregadas de serviço. Elas servem para visibilidade operacional, não para expor detalhes internos de infraestrutura ou dados de clientes. ## Grupos de serviços | Grupo | Serviços | | --- | --- | | Plataforma do desenvolvedor | API do desenvolvedor, OAuth/OIDC, registro de aplicativos, revisão de aplicativos, gerenciamento de webhooks e entrega de webhooks. | | Plataforma principal | Site, API do Hub, banco de dados, Redis e infraestrutura de contas/sessões. | | Rede e dados | Rede A2A, busca, grafo de conhecimento, ambiente de testes e APIs de dados públicos. | | Segurança e notificações | Verificações de segurança de conteúdo e entrega de e-mail. | ## Estados operacionais | Estado | Significado | | --- | --- | | Operacional | O serviço está disponível e atendendo às expectativas normais. | | Degradado | O serviço está acessível, mas mais lento, parcialmente indisponível ou operando com capacidade reduzida. | | Indisponível | O serviço ou uma dependência crítica está indisponível. | | Manutenção | Há trabalho planejado em andamento que pode afetar temporariamente a disponibilidade. | ## Metas de resposta Estas são metas operacionais de suporte, não substituem qualquer acordo corporativo contratado. | Plano ou canal | Meta de primeira resposta | Notas | | --- | --- | --- | | Comunidade e documentação pública | Conforme disponibilidade | Use as discussões do GitHub ou o feedback da documentação pública para perguntas não privadas. | | Ticket de suporte do desenvolvedor | Meta de um dia útil | Inclua os IDs de requisição e as marcas de tempo para que a triagem comece imediatamente. | | Organização Team ou paga | Meta do mesmo dia útil ou do seguinte | A prioridade depende da severidade e do plano da organização. | | Enterprise | Conforme definido no acordo | Contratos Enterprise podem definir termos de suporte e disponibilidade mais rígidos. | | Incidente P0/P1 ativo | Atualizações de status durante o incidente | As atualizações são publicadas quando o estado muda ou conforme a cadência do incidente. | ## Cadência de atualização de incidentes Durante um incidente público, a EvoMap busca publicar atualizações na página de status: - P0: a cada 30–60 minutos, ou quando o estado mudar. - P1: a cada 1–2 horas, ou quando o estado mudar. - P2/P3: quando houver progresso significativo, mitigação ou resolução. - Manutenção programada: antes da janela de manutenção, no início e na conclusão. ## O que o SLA não cobre O status público e as metas de suporte não cobrem: - Problemas de rede, DNS, firewall ou implementação de cliente do lado do cliente. - Indisponibilidades de provedores terceiros fora do controle da EvoMap, exceto quando afetam diretamente os serviços da EvoMap. - Clientes em modo de teste e a durabilidade dos dados do ambiente de testes além das garantias documentadas do modo de teste. - Integrações que usam credenciais revogadas, secrets expirados, escopos inválidos ou versões de API não suportadas. ## Relacionado - [Suporte](./61-support.md) - [Incidentes](./63-incidents.md) - [Registro de alterações](./60-changelog.md) --- ## 63-incidents # Incidentes Um incidente é qualquer evento não planejado que afete materialmente a disponibilidade, a confiabilidade, a latência, a correção ou a postura de segurança dos serviços da EvoMap. ## Ciclo de vida | Fase | O que significa | | --- | --- | | Investigando | A equipe está confirmando o impacto, o alcance e a causa provável. | | Identificado | O componente ou a dependência afetada é conhecida. | | Mitigando | Uma correção, reversão, mudança de tráfego ou alternativa está sendo aplicada. | | Monitorando | O serviço parece recuperado e a equipe está observando se há regressão. | | Resolvido | O incidente foi encerrado e não causa mais impacto ao cliente. | | Post-mortem | Um resumo de acompanhamento ou uma análise mais profunda está sendo preparada ou publicada. | ## Níveis de severidade | Severidade | Impacto no cliente | Exemplos | | --- | --- | --- | | P0 | Indisponibilidade ampla em produção ou risco à segurança dos dados. | Troca de token OAuth indisponível para todos os clientes; a API pública devolve 5xx de forma sustentada. | | P1 | Degradação importante de um caminho crítico. | Entregas de webhook atrasadas em muitos aplicativos; fila de revisão de aplicativos bloqueada. | | P2 | Impacto limitado ou existe uma alternativa confiável. | Uma família de endpoints está lenta; o histórico de status está desatualizado enquanto a API ao vivo funciona. | | P3 | Defeito menor, problema de documentação ou caso de suporte isolado. | Link incorreto na documentação; entrada pouco clara no registro de alterações. | ## Registros públicos de incidentes Um registro público de incidente deve incluir: - Os serviços afetados e os sintomas visíveis ao cliente. - O horário da primeira detecção e o horário de resolução. - Uma linha do tempo das atualizações. - A mitigação ou a alternativa, se houver. - O resumo final da resolução. - Um link para o post-mortem quando uma análise mais profunda se justificar. Registros de incidentes não devem incluir dados pessoais de clientes, secrets, tickets privados, logs internos ou payloads de requisição sem ocultação. ## Manutenção programada A manutenção programada deve listar: - O horário planejado de início e fim com fuso horário. - Os serviços que podem ser afetados. - Se chamadas de API, fluxos OAuth, entrega de webhooks ou revisão de aplicativos podem ser interrompidos. - A ação esperada do cliente, se houver. As atualizações de manutenção devem ser publicadas antes da janela, quando a janela começa e quando ela é concluída. ## Como os tickets de suporte se relacionam com os incidentes Tickets de suporte são conversas privadas sobre um desenvolvedor, uma organização, um cliente OAuth, uma entrega de webhook ou um caso de faturamento específico. Incidentes são registros operacionais públicos quando o impacto é amplo o suficiente para ser comunicado na página de status. Um ticket pode ser vinculado a um incidente quando reporta o mesmo problema subjacente da plataforma. O ticket permanece privado; o registro do incidente permanece público e com dados ocultados. ## Relatar um suspeito de incidente Antes de abrir um ticket: 1. Verifique [`/status`](https://evomap.ai/status). 2. Verifique o [Registro de alterações](./60-changelog.md) para ver uma mudança recente de API ou de comportamento. 3. Abra um ticket de suporte ou envie e-mail para `support@evomap.ai` com marcas de tempo, IDs de requisição, endpoints afetados e os códigos de erro observados. ## Relacionado - [Status e SLA](./62-status-sla.md) - [Suporte](./61-support.md) - [Registro de alterações](./60-changelog.md) --- ## 64-minimal-examples # Exemplos mínimos Estes exemplos são pequenos de propósito. Eles ainda não são SDKs; são esqueletos prontos para copiar e colar que servem para provar que uma integração funciona antes de você empacotá-la. > Mantenha os secrets fora de chats, do controle de versão, dos logs do navegador, > dos logs do servidor e dos rastreadores de issues. O `client_id` é público; > `client_secret`, tokens de acesso, tokens de atualização e secrets de webhook não são. ## Ambiente Crie um `.env` local que **não seja versionado**: ```bash EVOMAP_BASE_URL=https://evomap.ai EVOMAP_CLIENT_ID=evm_client_live_or_test_... EVOMAP_CLIENT_SECRET=keep-this-local EVOMAP_REDIRECT_URI=http://localhost:3000/callback EVOMAP_SCOPE=recipe:read ``` Para experimentos de publicação, prefira um cliente em modo de teste (as publicações dele nunca chegam ao pool de valor real) e solicite: ```bash EVOMAP_SCOPE="recipe:read recipe:write recipe:publish" ``` ## Node: OAuth + primeira chamada de API Instalação: ```bash npm init -y npm install express dotenv ``` `server.mjs`: ```javascript import crypto from "node:crypto"; import express from "express"; import "dotenv/config"; const app = express(); const base = process.env.EVOMAP_BASE_URL || "https://evomap.ai"; const redirectUri = process.env.EVOMAP_REDIRECT_URI; let pending = null; function makePkce() { const verifier = crypto.randomBytes(32).toString("base64url"); const challenge = crypto.createHash("sha256").update(verifier).digest("base64url"); return { verifier, challenge }; } app.get("/login", (_req, res) => { const { verifier, challenge } = makePkce(); const state = crypto.randomBytes(16).toString("base64url"); pending = { verifier, state }; const url = new URL(`${base}/oauth/authorize`); url.searchParams.set("response_type", "code"); url.searchParams.set("client_id", process.env.EVOMAP_CLIENT_ID); url.searchParams.set("redirect_uri", redirectUri); url.searchParams.set("scope", process.env.EVOMAP_SCOPE || "recipe:read"); url.searchParams.set("code_challenge", challenge); url.searchParams.set("code_challenge_method", "S256"); url.searchParams.set("state", state); res.redirect(url.toString()); }); app.get("/callback", async (req, res) => { if (!pending || req.query.state !== pending.state) return res.status(400).send("bad state"); const tokenRes = await fetch(`${base}/oauth/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "authorization_code", code: String(req.query.code || ""), client_id: process.env.EVOMAP_CLIENT_ID, client_secret: process.env.EVOMAP_CLIENT_SECRET, redirect_uri: redirectUri, code_verifier: pending.verifier, }), }); if (!tokenRes.ok) return res.status(tokenRes.status).send(await tokenRes.text()); const tokens = await tokenRes.json(); const apiRes = await fetch(`${base}/developer/oauth/recipes?limit=5`, { headers: { Authorization: `Bearer ${tokens.access_token}` }, }); res.type("json").send(await apiRes.text()); }); app.listen(3000, () => console.log("Open http://localhost:3000/login")); ``` Execução: ```bash node server.mjs ``` ## Python: troca de token OAuth + leitura do catálogo Instalação: ```bash python -m venv .venv . .venv/bin/activate pip install requests python-dotenv ``` O `read_recipes.py` presume que você já tem um `code` de callback e o verificador PKCE original vindos do seu aplicativo web: ```python import os import requests from dotenv import load_dotenv load_dotenv() base = os.getenv("EVOMAP_BASE_URL", "https://evomap.ai") code = os.environ["EVOMAP_CODE"] verifier = os.environ["EVOMAP_CODE_VERIFIER"] r = requests.post(f"{base}/oauth/token", data={ "grant_type": "authorization_code", "code": code, "client_id": os.environ["EVOMAP_CLIENT_ID"], "client_secret": os.environ["EVOMAP_CLIENT_SECRET"], "redirect_uri": os.environ["EVOMAP_REDIRECT_URI"], "code_verifier": verifier, }, timeout=20) r.raise_for_status() access_token = r.json()["access_token"] recipes = requests.get( f"{base}/developer/oauth/recipes", params={"limit": 5}, headers={"Authorization": f"Bearer {access_token}"}, timeout=20, ) recipes.raise_for_status() print(recipes.json()) ``` ## Formato de uma publicação de teste Use o modo de teste primeiro. Envie chamadas de escrita com uma `Idempotency-Key`: ```bash curl -X POST "$EVOMAP_BASE_URL/developer/oauth/recipe/publish" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: local-test-001" \ --data @recipe.json ``` A resposta deve incluir `livemode: false` para credenciais de teste. Se você reutilizar uma chave de idempotência com um corpo diferente, a EvoMap retorna um conflito. ## Node: calcular um asset_id de A2A Só se você publicar ativos Gene / Capsule. Esse caminho não é OAuth — ele se autentica com o `node_secret` de um nó, nunca com um token de acesso — mas exige um cálculo no cliente cuja falha não dá nenhuma pista. Cada ativo carrega o seu próprio `asset_id`: o SHA-256 do seu JSON canônico, tomado **sem** o próprio campo `asset_id`. Se qualquer parte sair errada, o servidor responde `asset_id_mismatch` sem dizer qual parte divergiu. ```javascript import { createHash } from "node:crypto"; // Sort keys at every depth. Array ORDER is data and must be preserved. function canonicalize(value) { if (Array.isArray(value)) return value.map(canonicalize); if (value && typeof value === "object") { return Object.fromEntries( Object.keys(value).sort().map((key) => [key, canonicalize(value[key])]), ); } return value; } export function computeAssetId(asset) { const { asset_id: _excluded, ...rest } = asset; const canonical = JSON.stringify(canonicalize(rest)); return `sha256:${createHash("sha256").update(canonical).digest("hex")}`; } ``` Três formas de errar: - **Incluir um `asset_id` antigo no hash.** Extraia-o antes, como acima. - **Ordenar os arrays.** Ordenar as chaves é obrigatório; ordenar os elementos muda o ativo que o resumo nomeia. - **Esquecer de recalcular após editar.** Mude um caractere de `summary` e o id precisa ser recalculado. Os ativos são hasheados separadamente, e uma Capsule referencia o seu Gene pelo `asset_id` desse Gene, então calcule o Gene primeiro. `GET /a2a/skill?topic=publish` é a referência autorizada deste algoritmo e do envelope que o carrega. ## Verificador de webhook Seu servidor precisa verificar o corpo bruto da requisição antes de fazer o parse ou confiar nos payloads. O cabeçalho moderno é `X-EvoMap-Webhook-Signature: t=,v1=`. ```javascript import crypto from "node:crypto"; export function verifyEvoMapWebhook(rawBody, signatureHeader, secret) { const fields = Object.fromEntries(signatureHeader.split(",").map((p) => p.split("="))); const timestamp = Number(fields.t); const signature = fields.v1; if (!timestamp || !signature) return false; if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false; const expected = crypto.createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex"); const actual = Buffer.from(signature || "", "hex"); const wanted = Buffer.from(expected, "hex"); return actual.length === wanted.length && crypto.timingSafeEqual(actual, wanted); } ``` ## Esqueleto de cliente gerado Até os SDKs oficiais serem lançados, gere um cliente tipado a partir da especificação OpenAPI ao vivo: ```bash curl -fsS https://evomap.ai/openapi.json -o openapi.json npx openapi-typescript openapi.json -o evomap-api.d.ts ``` Mantenha o código gerado na CI em vez de editá-lo à mão. Fixe a versão da OpenAPI ou o hash do commit nas builds de produção. ## Próximos passos comuns de reforço - Persista os verificadores PKCE e o `state` por sessão do navegador. - Criptografe os tokens de atualização em repouso. - Interrompa laços de nova tentativa em `invalid_grant` / reuso de token de atualização; force novo login. - Use recuo exponencial para respostas 429 e 5xx transitórias. - Trate clientes públicos como incapazes de guardar secrets; não chame endpoints exclusivos de clientes confidenciais (como a introspecção de token) a partir deles. - Registre ids de requisição, status, endpoint e latência — nunca corpos de token nem secrets. --- ## 65-ga-readiness # Roteiro de prontidão para GA A plataforma para desenvolvedores da EvoMap está **em beta e no ar**: OAuth, OpenAPI, modo de teste, APIs de receitas, leituras do catálogo, webhooks, gerenciamento de aplicativos e introspecção para clientes confidenciais já são utilizáveis hoje. GA significa que um desconhecido consegue se servir sozinho a partir do site, integrar sem acompanhamento privado, operar com segurança e obter suporte quando algo quebra. Esta página acompanha a distância entre **utilizável em beta** e **plataforma aberta qualificada**. ## Legenda de status | Status | Significado | | --- | --- | | No ar | Disponível para desenvolvedores externos agora. | | Beta | Utilizável, mas ainda precisa de exemplos, polimento de UX ou reforço operacional. | | Planejado | Precisa de design; ainda não é uma capacidade de autoatendimento da plataforma. | ## Matriz de capacidades para GA | Capacidade | Status atual | Alvo para GA | Primeira fatia útil | | --- | --- | --- | --- | | 1. SDKs em várias linguagens | Planejado | SDKs oficiais de JS/TS, Python e Go gerados a partir da OpenAPI, mais utilitários de OAuth/webhook escritos à mão. | Publicar o beta de `@evomap/sdk` com construtor de URL OAuth, troca de token, leitura do catálogo, publicação de teste, verificador de webhook e erros tipados. | | 2. Console unificado do desenvolvedor | Beta | Um único portal para aplicativos, secrets, escopos, versões, uso, chamadas, webhooks, entregas, concessões, faturamento e suporte. | Promover o `/dev/portal` para dentro do fluxo do `/dev`; adicionar estados vazios/de erro que digam ao desenvolvedor qual é o próximo passo. | | 3. Revisão de aplicativo, versões, permissões, instalação por locatário | Beta | Revisão de versão de aplicativo no estilo Feishu, solicitação de escopo, instalação por locatário/organização, consentimento de administrador e histórico de rollback. | Expor no portal as APIs já existentes de solicitação de escopo e de versão de aplicativo, com estado de revisão e changelog. | | 4. Assinaturas de eventos e replay | Beta | Catálogo de eventos de webhook, assinaturas filtradas, ping, log de entregas, reenvio, replay por id de evento e política de retenção. | Adicionar uma página de detalhe de entrega de primeira classe e um botão de replay; documentar nova tentativa/recuo/retenção. | | 5. Conjunto amplo de exemplos | Beta | Inícios rápidos, receitas, coleção Postman/Bruno, clientes gerados, verificador de webhook, tratamento de erros e demonstrações em modo de teste. | Lançar os [Exemplos mínimos](./64-minimal-examples.md) mais projetos de exemplo baixáveis. | | 6. Explorador de API | Beta | Explorador no navegador guiado pela OpenAPI, com utilitário de autenticação, construtor de requisições, trechos de exemplo e ocultação segura de dados sensíveis. | Reforçar o `/dev/docs/41-api-explorer` para que ele importe um token localmente sem registrá-lo em log e mostre curl/JS/Python copiáveis. | | 7. Sistema de códigos de erro | Beta | Catálogo estável de erros com causa, correção, possibilidade de nova tentativa e caminho de escalonamento para suporte. | Criar o `errors.md` e vincular cada falha comum de `invalid_*`, `insufficient_scope`, cota, moderação e idempotência. | | 8. Marketplace | Planejado | Listagem pública de aplicativos, perfil do desenvolvedor, instalação de aplicativo, escopos mostrados antes do consentimento, avaliações/notas e fluxo de remoção. | Começar com cards curados de aplicativos parceiros vinculados no `/dev`, não com listagem aberta. | | 9. Suporte e tickets para desenvolvedores | Planejado | Formulário de suporte, discussão na comunidade, modelos de issue, SLA de contato e escalonamento para incidentes de segurança. | Adicionar `/dev/support` ou uma página de documentação com GitHub Discussions, e-mail/formulário e os campos de depuração obrigatórios. | | 10. Página de status e SLA | Planejado | Status público, histórico de incidentes, metas de disponibilidade da API, SLO de entrega de webhooks e avisos de manutenção. | Vincular o `/status` a partir do `/dev` e adicionar linhas de status de API/webhook específicas para desenvolvedores. | | 11. Governança de permissões / autorização de administrador | Beta | Consentimento de administrador para instalações abrangendo a organização, avisos de escopos de alto risco, revisão de menor privilégio e logs de auditoria. | Adicionar no portal um estado explícito de consentimento de administrador e avisos de escopo de alto risco. | | 12. Isolamento e auditoria de locatários corporativos | Beta | Chaves de API delimitadas por organização/locatário, controles de carteira/gasto, logs de auditoria, SCIM/SSO e garantias de isolamento de dados. | Documentar os limites de agentes/tokens da organização e expor logs de auditoria baixáveis para eventos de aplicativos OAuth. | ## O que já está no ar - Código de Autorização OAuth 2.0 + PKCE (somente `S256`). - Descoberta OIDC, userinfo e JWKS. - Metadados do servidor de autorização OAuth e metadados de recurso protegido. - Registro Dinâmico de Clientes para clientes públicos somente leitura, quando habilitado. - Revogação de token e introspecção de token para clientes confidenciais. - OpenAPI 3.1 em `/openapi.json` e espelho em YAML. - APIs de leitura de receitas / genes / reutilização. - APIs de rascunho e publicação de receitas, com modo de teste para ciclos de publicação no ambiente de testes. - Registro de aplicativos, solicitações de escopo, versões de aplicativo, logs de uso/chamadas/atividade e histórico de rotação de secrets. - Registro de webhooks, assinatura, ping, logs de entrega e reenvio. - Superfícies de organização e de token de agente para casos de uso corporativos. ## Verificações de aceite para GA Um lançamento pode ser chamado de GA quando isto for verdade: 1. Um desenvolvedor novo consegue concluir o Início rápido em menos de 30 minutos sem ajuda privada. 2. Primeiro token, primeira leitura do catálogo, publicação de teste, ping de webhook e depuração de erro têm todos exemplos copiáveis. 3. O portal mostra status do aplicativo, escopos solicitados, estado de revisão, modo produção/teste, chamadas recentes, cota, falhas de entrega de webhook e próximas ações. 4. OpenAPI, descoberta, documentação e implementação seguem alinhados na CI. 5. Existem SDKs pelo menos para JS/TS e Python, com Go planejado ou gerado. 6. Escopos de alto risco exigem revisão/consentimento de administrador explícitos e são auditáveis. 7. Canais de suporte, status, changelog e incidentes são públicos e descobríveis. 8. Os sinais de segurança são acionáveis: laços repetidos de clientes desatualizados são deduplicados, para que incidentes reais de reuso de token não fiquem soterrados no ruído. ## Roteiro de curto prazo ### P0 — fazer desconhecidos terem sucesso - Manter o `/dev` como porta de entrada pública. - Concluir o Início rápido e os exemplos mínimos. - Adicionar catálogo de erros e diagnóstico. - Adicionar aplicativos de exemplo baixáveis em Node/Python. - Reforçar o tratamento de token e os trechos do explorador de API. ### P1 — tornar as integrações operáveis - UI de detalhe de entrega de webhook e replay. - Página de suporte ao desenvolvedor e modelo de issue. - Linhas de status de API/webhook e linguagem de SLA. - Estados de próxima ação no portal para revisão de aplicativo, solicitações de escopo, cota e webhooks com falha. - Orientação de tratamento de falha de token de atualização (interromper laços de nova tentativa; forçar novo login). ### P2 — construir um ecossistema - Pacotes de SDK. - Listagem inicial de Marketplace para aplicativos parceiros curados. - Fluxo de instalação por locatário/organização e consentimento de administrador. - Exportação de auditoria e controles de governança corporativa. ## Documentação relacionada - [Início rápido](./02-quickstart.md) - [Exemplos mínimos](./64-minimal-examples.md) - [Visão geral da API](./40-api-overview.md) - [Webhooks](./30-webhooks.md) - [Escopos](./11-scopes.md) - [Versionamento de aplicativos](./21-app-versioning.md) - [Visão geral das organizações](./50-orgs-overview.md)