## Visão geral

Dashboard Apps nativos têm três contratos de integração separados:

- O **SDK V2** do navegador entrega contexto limitado por capacidades, eventos somente leitura,
  comandos seguros de quadro e identidade assinada opcional para um backend próprio.
- A **API REST** da conta permite a administradores ou funções personalizadas com `integration_manage`
  criar, consultar, atualizar, reordenar e excluir
  apps e instalações.
- O **servidor MCP** da conta expõe o mesmo contrato de instalações em quatro ferramentas opcionais
  de **Canais e integrações → Dashboard Apps**.

O SDK autentica somente a identidade curta do app incorporado; não oferece um proxy genérico de API.
Escritas de negócio devem passar pelo seu backend, autenticado na API REST com um token de privilégio mínimo.

## Pré-requisitos

- `dashboard_apps_native_surfaces` habilitada na conta.
- Dashboard App HTTPS em origem diferente do dashboard da Conversa Labs para o modo V2.
- Uma pessoa administradora ou função personalizada com `integration_manage` e um `api_access_token`
  para gerenciamento REST.
- Para MCP, perfil MCP cujo usuário atuante tenha essa permissão e que inclua **Dashboard Apps**.
- CSP `frame-ancestors` permitindo a origem exata da Conversa Labs, sem `X-Frame-Options` conflitante.

## Passo a passo

### 1. Conecte o SDK V2 no navegador

Importe o SDK pelo caminho estável e versionado da sua implantação da Conversa Labs. Ele redireciona
para o build atual com fingerprint e expõe exportações nomeadas do módulo JavaScript.

```js
import { connect } from '/dashboard-app-sdk/v2.js';

try {
  // A origem e o installationId são descobertos dos parâmetros cl_* injetados pelo host.
  // Também podem ser fixados explicitamente para testes controlados.
  const client = await connect();
  const stop = await client.subscribe(
    ['context.initialized', 'conversation.changed', 'theme.changed'],
    event => console.log(event.context_revision, event.data)
  );

  await client.setHeight(520);
  document.querySelector('#documentacao').addEventListener('click', () => {
    client.openLink('https://app.exemplo.com/docs');
  });

  // Depois: await stop(); client.disconnect();
} catch (error) {
  console.error(error.code);
}
```

Use as importações nomeadas do módulo mostradas acima. Elas são o contrato estável para novos
aplicativos. Se o seu app não consegue consumir exportações nomeadas, o mesmo arquivo também expõe
`window.ConversaLabsDashboardAppSDK`, com as mesmas funções.

**Chame `connect()` assim que a página carregar**, não atrás de autenticação ou de uma ida ao seu
backend. O host inicia o handshake quando o quadro termina de carregar e insiste por até 10 segundos;
um aplicativo que só começa a escutar depois desse prazo recebe `handshake_timeout`. Um `import`
estático no topo de um `<script type="module">` é a forma suportada. `import()` dinâmico funciona,
desde que aguardado imediatamente — não deixe para carregar o SDK depois de renderizar a tela.

`connect` lê `cl_dashboard_origin` e `cl_installation_id` da URL de lançamento, valida a origem exata,
negocia o protocolo `2.0` e resolve depois do handshake. O cliente
expõe `installationId`, `capabilities`, `connected`, `subscribe`, `unsubscribe`, `setHeight`,
`openLink`, `getIdentityAssertion` e `disconnect`.

Os eventos incluem inicialização de contexto, conversa, status/atribuição/labels, criação/atualização
de mensagem, contato/usuário atual, idioma/tema/permissões e instalação. A superfície de conversa é
omnichannel e funciona para e-mail, WhatsApp, SMS e outros inboxes; a barra lateral omite conversa e
contato. Programe para as `capabilities`, campos opcionais e `context_revision` crescente;
ressincronize em vez de aplicar dados antigos.

#### Identificação segura para n8n e outros backends

Não envie identidade nem credenciais pela query string do iframe. No evento `context.initialized`, use:

- `account.id` e `account.name` para identificar a conta;
- `current_user.id`, `current_user.name`, `current_user.role` e `current_user.avatar_url` para identificar
  a pessoa que abriu o app;
- `installation.id`, `surface` e, na barra lateral, `sidebar_category` e `sidebar_icon` para identificar
  a instalação, o local, sua categoria nativa e o ícone escolhido;
- na superfície de conversa, `conversation`, `contact`, `permissions` e eventos de mensagem para o
  contexto operacional.

O bridge nunca expõe tokens de usuário, sessão, CSRF, REST, MCP ou credenciais do provedor do canal.
Quando concedidos por instalação, `current_user:email` adiciona o e-mail do usuário atual;
`contact:email` e `contact:phone` adicionam os dados do contato na conversa. Sem a capacidade, o campo
é omitido em contexto e eventos.

Para um backend próprio ou webhook n8n confirmar a identidade sem confiar em IDs enviados pelo
navegador, peça uma asserção curta:

```js
const identity = await client.getIdentityAssertion();
await fetch('https://app.exemplo.com/api/dashboard-session', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(identity),
});
```

O backend do app envia `assertion` por POST para a `introspection_url` recebida, opcionalmente com
`expected_origin: "https://app.exemplo.com"`. Uma resposta ativa contém claims de conta, usuário,
instalação, superfície e capacidades. A asserção expira em dois minutos e é revalidada contra feature,
instalação, usuário ativo, associação, público e origem a cada introspecção. O verificador público
rejeita asserções maiores que 16 KiB antes de verificar a assinatura. Ela **não** é token REST/MCP e não deve
ser armazenada nem usada como bearer. Para dados/escritas adicionais, o backend usa sua própria
credencial REST de privilégio mínimo ou perfil MCP restrito.

### 2. Gerencie instalações por REST

Todos os caminhos pertencem à conta. Com a feature desligada, a resposta é `404 resource_not_found`;
um membro autenticado sem permissão de gerenciamento recebe `403 forbidden` nessas operações.

```bash
# Lista instalações visíveis; administradores também recebem audience.
curl -sS "https://suporte.exemplo.com/api/v1/accounts/1/dashboard_app_installations?surface=conversation" \
  -H "api_access_token: API_TOKEN"

# Cria uma instalação. dashboard_app_id não pode ser alterado depois.
curl -sS -X POST "https://suporte.exemplo.com/api/v1/accounts/1/dashboard_app_installations" \
  -H "api_access_token: API_TOKEN" -H "Content-Type: application/json" \
  --data '{"dashboard_app_installation":{"dashboard_app_id":42,"surface":"conversation","compatibility_mode":"v2","enabled":true,"position":0,"capabilities":["account:read","current_user:read","current_user:email","permissions:read","appearance:read","installation:read","conversation:read","contact:read","contact:email","contact:phone","messages:read","identity:assertion"],"audience":{"type":"any","roles":["administrator"],"team_ids":[7],"user_ids":[]}}}'

# Atualiza ou reordena com a lock_version da resposta mais recente.
curl -sS -X PATCH "https://suporte.exemplo.com/api/v1/accounts/1/dashboard_app_installations/9" \
  -H "api_access_token: API_TOKEN" -H "Content-Type: application/json" \
  --data '{"dashboard_app_installation":{"sidebar_category":"applications","sidebar_icon":"rocket","position":1,"lock_version":3}}'

# Exclui.
curl -sS -X DELETE "https://suporte.exemplo.com/api/v1/accounts/1/dashboard_app_installations/9" \
  -H "api_access_token: API_TOKEN"
```

A lista retorna `{ "payload": [...], "meta": { "revision": N } }`; criação, detalhe e atualização
retornam `{ "payload": { ... } }`; exclusão retorna `204`. A instalação contém app id/título/URL,
`surface`, `compatibility_mode`, `enabled`, `visible` operacional, `position`, `sidebar_category` e
`sidebar_icon` na barra lateral, `capabilities`, `audience` apenas para administradores,
`transport_security`, `warnings`, `lock_version` e `updated_at`.

`POST /api/v1/accounts/:account_id/dashboard_app_installations/:id/identity_assertion` é a chamada
autenticada usada internamente por `getIdentityAssertion`; respeita feature, instalação ativa, V2/dual,
público e capacidades. Aplicativos devem preferir o SDK. A validação server-to-server usa o endpoint
público `POST /dashboard-apps/v2/assertions/introspect` descrito acima.

Também é possível criar app e instalações iniciais de forma atômica enviando `installations` no
`POST /dashboard_apps`. Com a feature ligada, omitir a propriedade cria a compatibilidade padrão
`conversation + legacy + all`; um array vazio cria somente o app. Com a feature desligada, enviar
`installations` é rejeitado e omitir a propriedade preserva a criação legada. Cada app pode ter uma
instalação por superfície.

### 3. Use as ferramentas MCP da conta

Habilite **Dashboard Apps** no perfil MCP. O módulo permanece no grupo `channels_integrations`,
preserva as ferramentas existentes de definição de app e adiciona:

| Ferramenta | Efeito | Perfil somente leitura |
|---|---|---|
| `list_dashboard_app_installations` | Lista instalações visíveis ao usuário atuante | Disponível |
| `create_dashboard_app_installation` | Cria uma instalação | Oculta |
| `update_dashboard_app_installation` | Atualiza categoria, ícone, posição, público ou bridge | Oculta |
| `delete_dashboard_app_installation` | Exclui uma instalação | Oculta |

MCP usa o mesmo escopo da conta, política, público, feature e erros de validação da API REST. O detalhe
REST e o alias PUT não viram ferramentas separadas: a lista é a leitura canônica e PATCH é a atualização
e reordenação canônica. A mintagem de identidade não vira ferramenta MCP: ela pertence à sessão humana
incorporada, enquanto o cliente MCP já possui seu próprio principal autenticado.

## Configurações e opções

- Superfícies: `conversation` ou `sidebar`.
- Modos: `legacy`, `v2` ou `dual`.
- Capacidades: núcleo obrigatório; conversa/contato/mensagens somente em `conversation`; dados pessoais
  opcionais `current_user:email`, `contact:email`, `contact:phone`; identidade opcional
  `identity:assertion`. Omitir aplica o padrão completo e útil da superfície; um array vazio explícito
  é rejeitado com `invalid_capabilities`. Remova apenas concessões opcionais e preserve todas as
  capacidades obrigatórias da superfície selecionada.
- Público: `{ "type": "all" }` ou `{ "type": "any", "roles": [...], "team_ids": [...],
  "user_ids": [...] }`. Funções válidas: `agent` e `administrator`; `any` exige ao menos um seletor
  não vazio e concede acesso se qualquer seletor corresponder.
- Categoria lateral: `support`, `contacts_crm`, `applications`, `commercial`, `productivity`,
  `automation`, `growth` ou `analytics_config`; é aceita somente em `sidebar` e o padrão é
  `productivity`. O grupo `applications` aparece logo abaixo de Contatos & CRM e é omitido quando vazio.
- Ícone lateral: `panels_top_left`, `layout_dashboard`, `app_window`, `boxes`, `briefcase`, `bot`,
  `calendar_days`, `chart_no_axes_combined`, `circle_dollar_sign`, `clipboard_list`, `database`,
  `folder`, `globe_2`, `headphones`, `life_buoy`, `messages_square`, `package`, `rocket`,
  `shopping_bag`, `sparkles`, `workflow` ou `wrench`; aceito somente em `sidebar`, com
  `panels_top_left` como padrão.
- Posição: índice a partir de zero, normalizado por superfície e, na barra lateral, por categoria.
- Concorrência: envie `lock_version`; `409 stale_installation` pede recarga e nova tentativa.
- Erros estáveis: `invalid_audience`, `invalid_capabilities`, `invalid_sidebar_category`, `invalid_sidebar_icon`,
  `installation_already_exists`, `invalid_dashboard_app_url`,
  `dashboard_app_migration_required`, `unsafe_same_origin_v2` e `resource_not_found`. O erro de
  migração exige um único quadro HTTP(S); query string e fragmento são aceitos.
- Limites do SDK: mensagens de 64 KiB, 32 assinaturas, 30 comandos por 10 segundos e altura entre
  160–2.000 px.
- Prazos do handshake: são **dois**, independentes. O painel abre a janela quando o quadro carrega e
  reenvia o convite por até 10 segundos. O `connect()` do SDK tem o próprio limite de 10 segundos,
  contado a partir da chamada, ajustável por `connect({ timeoutMs })`. Aumentar o do SDK não ajuda:
  o painel para de insistir antes.
- Ciclo de vida: o SDK dispara eventos de janela `conversalabs.dashboard-apps:bridge.connected`,
  `:bridge.paused`, `:bridge.resumed` e `:context.resynced`. Ouça-os para pausar trabalho quando a aba
  sai de foco e para ressincronizar. O código `disconnected` significa que todo comando seguinte será
  rejeitado; refaça a conexão em vez de repetir o comando.

### Códigos de erro e a primeira coisa a conferir

| Código | Significa | Comece por |
|---|---|---|
| `handshake_timeout` | O painel insistiu por 10 s e o app não respondeu | O app chama `connect()` no carregamento? O bundle carregado expõe `connect`? |
| `invalid_origin` | Origem configurada ou de destino inválida | URL HTTP(S) sem credenciais; em V2 a origem precisa ser diferente da do painel |
| `frame_blocked` | O navegador recusou a incorporação | `frame-ancestors` e `X-Frame-Options` no servidor do app |
| `insecure_transport_blocked` | App HTTP dentro de painel HTTPS | Publique o app em HTTPS |
| `unsupported_version` | App e painel não compartilham a versão da bridge | Use o SDK da mesma implantação |
| `unsupported_command` | Comando fora da allowlist | Use apenas os comandos anunciados |
| `invalid_payload` | Mensagem fora do contrato | Versão, sequência, campos e tamanho |
| `invalid_sequence` | Mensagem fora de ordem | Não reimplemente o protocolo à mão; use o SDK |
| `rate_limited` | Mais de 30 comandos em 10 s | Reduza frequência e agrupe assinaturas |
| `disconnected` | A ponte fechou | Reconecte; comandos pendentes não voltam |
| `user_denied` | A pessoa recusou a confirmação | Esperado em `openLink`; não insista |
| `capability_denied` | Capacidade não concedida | Habilite na instalação; dados de contato só existem em conversa |
| `identity_unavailable` | Não foi possível emitir a asserção | Feature, modo V2/dual, instalação ativa e público |

## Casos de uso

- Renderizar contexto da conversa e manter alterações de CRM em backend auditável.
- Provisionar instalações por um serviço administrativo interno.
- Permitir que um perfil de IA somente leitura inventarie apps sem direitos de escrita.
- Usar MCP de escrita em perfil administrador restrito para automação controlada.

## Dicas, limites e boas práticas

- Fixe `dashboardOrigin` na origem HTTPS exata. Não use `*`, não aceite origens `postMessage` não
  confiáveis e não reimplemente o protocolo quando houver SDK.
- Query string e fragmento estáticos são preservados. Em V2/dual, o host acrescenta e sobrescreve
  apenas `cl_dashboard_origin`, `cl_installation_id`, `cl_account_id`, `cl_surface`, `cl_protocol` e
  `cl_locale`; trate todo nome `cl_*` como reservado. O host remove valores `cl_*` estáticos antes de
  escrever esses seis parâmetros. Nunca coloque tokens, asserções, segredos ou
  dados pessoais na URL ou no bundle do app.
- Mantenha credenciais REST/MCP no servidor, rotacione-as e separe perfis de inventário dos administrativos.
- Autorize o público no servidor. Esconder uma aba no iframe não é autorização.
- `openLink` sempre mostra uma confirmação nativa do host. O link só abre depois que a pessoa clica
  em **Abrir link**; um timestamp enviado pelo iframe nunca é aceito como prova de gesto.
- Trate códigos de `DashboardAppSDKError`, desconexões, limites e ressincronização explicitamente.
- `insecure_http` é sinal de migração, não aprovação para produção.

## Solução de problemas

- **`handshake_timeout`**: o painel reenviou o convite por 10 segundos e o aplicativo não respondeu.
  Comece pelo app, não pela rede: ele chama `connect()` assim que a página carrega? O bundle que ele
  carregou expõe `connect`? Só depois confira a origem e o modo V2/dual. Um quadro que fica em branco
  é outro problema — veja `frame_blocked`.
- **O modo Dual esconde a falha da V2**: com Dual, o atendimento segue pelo lane legado e a superfície
  aparece pronta mesmo com a bridge V2 morta. O diálogo **Testar** mostra o resultado de cada bridge
  separadamente; para ver o erro cru, mude a instalação para **V2** temporariamente.
- **`invalid_origin`**: use URL HTTP(S) sem credenciais embutidas e mantenha a V2 numa origem diferente
  da do painel.
- **`unsupported_version` ou `unsupported_command`**: use o SDK da mesma implantação e apenas
  capacidades anunciadas.
- **`capability_denied`**: habilite a capacidade pedida na instalação; contato/e-mail do contato
  existem somente em `conversation`.
- **`identity_unavailable` ou introspecção inativa**: obtenha uma nova asserção e confira feature,
  modo V2/dual, instalação ativa, público, associação e origem exata.
- **`rate_limited` ou `invalid_payload`**: reduza frequência, tamanho e assinaturas.
- **REST/MCP retorna 404**: confira conta, instalação e feature. O público também pode ocultar o recurso.
- **REST/MCP retorna 403**: usuário autenticado ou atuante não é administrador nem possui uma função
  personalizada com `integration_manage`.
- **409 ao atualizar**: busque o estado atual e repita com sua `lock_version`.
- **422 ao criar/atualizar**: confira o `error`, URL, par app/superfície único, categoria lateral,
  público e origem do V2.

## Veja também

- [Dashboard Apps: superfícies nativas, público e segurança](/hc/ajuda/articles/administration-dashboard-apps-native-surfaces-pt-br)
- [API REST, tokens e webhooks](/hc/ajuda/articles/api-developers-rest-tokens-webhooks-pt-br)
- [Referência da API (Swagger / OpenAPI)](/hc/ajuda/articles/api-developers-swagger-reference-pt-br)
- [MCP nativo: conexões, servidor e clientes](/hc/ajuda/articles/api-developers-mcp-server-and-client-pt-br)