# Quem pode ver o quê

Um painel que qualquer pessoa com a URL pode ler é uma captura de tela com passos extras. A razão para manter painéis em uma plataforma mantida, em vez de em uma pasta de HTML exportado, é que a plataforma lembra quem tem permissão para abri-los — e continua lembrando depois que a pessoa que os construiu segue em frente.

Esta página é o mapa honesto desse modelo: o que ele aplica hoje, onde ele para, e quais partes você pode alcançar a partir de código.

## Os três papéis[​](#os-três-papéis "Link direto para Os três papéis")

Toda associação a uma conta carrega exatamente um papel:

| Papel            | Valor     | O que significa                                                                       |
| ---------------- | --------- | ------------------------------------------------------------------------------------- |
| **Admin**        | `admin`   | Ignora toda verificação de direitos de acesso. Gerencia membros, grupos e concessões. |
| **Criador**      | `creator` | Pode criar e alterar. Sujeito a direitos de acesso.                                   |
| **Visualizador** | `viewer`  | Somente leitura. Recusado em qualquer rota que altera dados.                          |

Três coisas sobre isso valem a pena saber antes de você projetar algo em torno disso.

**O papel vive na linha de associação, não no token.** O JWT não carrega nenhuma claim de papel, então toda decisão de papel é uma leitura no banco de dados sobre o par conta/usuário. Isso é um pouco mais lento e muito mais difícil de falsificar.

**Ele falha fechado.** Uma associação cujo papel armazenado não é reconhecido — um valor legado, um erro de digitação — resolve para `viewer`, não para admin.

**Admin é uma substituição, não uma patente máxima.** Um admin não é "um criador com mais concessões": a verificação de permissão atalha diretamente para *permitir* antes que qualquer concessão seja consultada. Você não pode negar um admin o acesso a uma pasta.

Uma chave de API age como a pessoa que a criou

Uma chave de API resolve para o usuário que a criou, **com o papel desse usuário**. Uma chave gerada por um admin é uma chave de admin, e tudo abaixo se aplica a ela exatamente como se aplica a essa pessoa no navegador. Não há um escopo de permissão separado e mais restrito para chaves, e uma chave não expira quando o papel de quem a criou é rebaixado — revogue a chave.

Vinte e quatro rotas são deliberadamente fechadas para chaves e precisam de uma sessão de navegador real: cobrança e pagamento (16 delas), o próprio gerenciamento de chaves de API, a leitura da sua assinatura, exclusão de conta, troca de senha, telemetria e assinatura de token de incorporação. **Tudo o mais aceita uma chave** — incluindo toda rota de direitos de acesso, grupo, pasta, painel e conjunto de dados.

**Uma exceção deliberada.** Tornar um painel `public` é controlado por uma chave da conta, `apiKeysMayPublish`, que **tem `false` como padrão** e só pode ser ativada por um admin em um navegador. Então uma chave — e, portanto, um agente que a possui — pode construir, publicar e compartilhar dentro da sua conta, mas por padrão não pode ser o que torna algo legível pelo mundo todo. Veja [Publique e compartilhe](/pt-BR/guides/publish-and-share.md#going-public-is-gated-separately-and-an-agent-cannot-do-it-alone).

## Grupos[​](#grupos "Link direto para Grupos")

Um grupo é um conjunto nomeado de usuários dentro de uma conta. Você concede ao grupo; a associação muda sem tocar na concessão.

Dois limites, ambos deliberados:

* **Grupos contêm usuários, não outros grupos.** Não há aninhamento, então não há hierarquia de grupos para se preocupar.
* **Leituras são abertas a todo membro da conta; escritas são exclusivas de admin.** Qualquer membro pode listar os grupos e ver quem está neles. Somente um admin pode criar, renomear, excluir ou alterar a associação.

Grupos *estão* no SDK:

```
from vibedasher.models import GroupCreate, GroupUpdate



created = client.groups.create(GroupCreate(name="analytics", description="Read-only analysts"))

client.groups.update(created.group.id, GroupUpdate(user_ids_to_add=["<user-id>"]))



for g in client.groups.list().groups:

    print(g.name, g.user_count)
```

```
const created = await client.groups.create({ name: 'analytics', description: 'Read-only analysts' });

await client.groups.update(created.group.id, { userIdsToAdd: ['<user-id>'] });



const { groups } = await client.groups.list();
```

## Direitos de acesso — a concessão é na pasta[​](#direitos-de-acesso--a-concessão-é-na-pasta "Link direto para Direitos de acesso — a concessão é na pasta")

Esta é a frase mais importante desta página, e a que mais provavelmente vai te surpreender:

> **Uma concessão nomeia uma pasta, nunca um painel ou conjunto de dados individual.**

Um direito de acesso é uma linha no formato:

```
(account, folder, resource type, permission, principal) → allow | deny
```

* **tipo de recurso** — `dataset` ou `viz`. São os dois que uma concessão pode nomear; veja [O que uma concessão pode nomear](#what-a-grant-can-name) para o que governa o resto.
* **permissão** — `view`, `edit`, `delete`, `create`.
* **principal** — um usuário ou um grupo.
* **allow / deny** — os dois tipos de acesso.

Um painel é coberto por uma concessão porque ele *está* na pasta concedida. Mova-o para outra pasta e ele passa a ser governado pelas concessões dessa pasta. Não há concessão por painel para perder de vista, que é a metade boa desse design; a outra metade é que **a pasta é a menor unidade que você pode conceder**, então uma pasta que você compartilha é uma pasta que você compartilha inteiramente.

### Isso não é um teto para o quão estreito um compartilhamento pode ser[​](#that-is-not-a-ceiling-on-how-narrow-a-share-can-be "Link direto para Isso não é um teto para o quão estreito um compartilhamento pode ser")

A frase acima é sobre a *concessão*, não sobre o compartilhamento. Uma pasta é a menor coisa que você pode nomear em uma concessão — e você decide o que há dentro de uma pasta. Para compartilhar exatamente um painel, dê a ele uma pasta própria, o que são três chamadas que seu agente já tem:

```
create_folder(name="Q3 board pack")

move_to_folder(resource_type="viz", resource_id=<vizId>, folder_id=<newFolderId>)

grant_access(folder_id=<newFolderId>, resource_type="viz",

             principal_id=<userId>, principal_type="user", permissions=["view"])
```

`resource_type` é `dataset` ou `viz` na movimentação, `principal_type` é `user` ou `group`, e `access_type` é `allow` por padrão — `deny` é o outro valor, e ele vence qualquer allow.

Essas três são ferramentas MCP, e o MCP é a via suportada para isso. **As rotas de access-rights são deliberadamente mantidas fora do SDK publicado e da referência de API** — são encanamento de controle de acesso cuja forma não assumimos, então não existe método de cliente gerado para elas e nenhum deve ser usado. `create_folder` é a das três que tem equivalente no SDK, `client.folders.create`.

Mover um painel muda quais concessões o governam e nada mais — o painel mantém seu id, e [seu link não é afetado](/pt-BR/guides/publish-and-share.md). Portanto «compartilhar por painel é impossível» é a conclusão errada a tirar da regra da pasta: são três chamadas, e a pasta é como você expressa isso.

### As duas metades de uma concessão de painel[​](#both-halves-of-a-dashboard-grant "Link direto para As duas metades de uma concessão de painel")

Um painel e os dados por baixo dele são governados por **tipos de recurso separados**, então um compartilhamento que nomeia apenas um deles está feito pela metade. `viz:view` permite que alguém abra o painel; `dataset:view` é o que permite que seus blocos retornem números. A próxima seção é o motivo pelo qual isso não é um detalhe que você pode adiar.

### Como uma decisão é de fato tomada[​](#como-uma-decisão-é-de-fato-tomada "Link direto para Como uma decisão é de fato tomada")

Em ordem. A primeira regra que corresponder vence:

1. **Admin** → permite. Incondicionalmente, antes que qualquer concessão seja lida.
2. **O principal interno `system`** → permite. Essa é a identidade própria de worker da plataforma (jobs de atualização, execuções agendadas); não é uma conta em que você possa fazer login.
3. **Propriedade** → permite. O usuário que é proprietário da linha sempre pode lê-la e alterá-la.
4. Caso contrário, as concessões da pasta decidem, e **uma única negação vence qualquer permissão.**

Vale a pena declarar com precisão que a negação vence, porque isso é mais restrito do que parece. Uma negação remove uma pasta do conjunto que foi concedido a você — ela não subtrai das regras 1 a 3. **Você não pode negar um admin, e não pode negar alguém o acesso a uma linha da qual ele é proprietário.** Se seu modelo mental é "negação é o botão de emergência", não é: é uma subtração apenas dentro da camada de concessão.

### Dois comportamentos que vão te morder[​](#dois-comportamentos-que-vão-te-morder "Link direto para Dois comportamentos que vão te morder")

**Concessões não se propagam para subpastas.** A verificação é um teste plano de pertencimento a conjunto sobre o próprio `folder_id` do recurso. Pastas têm sim uma relação pai/filho, e a interface a desenha como uma árvore — mas nenhum código de autorização a percorre. Uma concessão em *Marketing* **não** alcança *Marketing / Q3*. Conceda cada pasta que você pretende conceder.

**Uma negação em `create` costumava não fazer nada — corrigido em 2026-09-08.** A verificação de permissão de criação lia apenas as linhas `allow`; o direito básico de um Criador de criar na raiz da conta ou em uma pasta da qual é proprietário, e uma linha `allow` explícita em outro lugar, ambos ignoravam completamente qualquer `deny`. Uma linha `create` / `deny` explícita de um admin agora sobrepõe as duas — a mesma regra "uma única negação vence qualquer permissão" que o resto desta página descreve para view/edit/delete. Admins continuam inegáveis, igual a qualquer outra permissão aqui.

### Quem pode alterar uma concessão[​](#quem-pode-alterar-uma-concessão "Link direto para Quem pode alterar uma concessão")

Um **admin da conta**, ou o **proprietário da pasta** onde a concessão está. Ler as concessões é aberto a qualquer membro da conta: o estado de compartilhamento é renderizado no app em páginas que qualquer membro pode abrir, então a leitura é deliberadamente irrestrita. Trate a própria lista de concessões como informação visível para toda a conta.

## O que uma concessão pode nomear[​](#what-a-grant-can-name "Link direto para O que uma concessão pode nomear")

**Dois tipos de recurso aceitam concessão: `viz` (painéis) e `dataset`.** Construa seu modelo de governança com esses dois, guardados em pastas — é a forma que o modelo aplica e a única que o diálogo oferece. `POST /v1/access-rights` recusa qualquer outro tipo de recurso com um `400`, no momento em que você o escreve e não no momento em que alguém conta com ele.

Três coisas são governadas por algo que não é uma concessão, e saber qual por quê é todo o sentido desta seção:

| Recurso                        | O que o governa                                                                                                                                                   |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Pasta**                      | A participação na conta para leituras; o papel para mutações. Uma pasta é o contêiner que uma concessão *nomeia*, não algo contra o qual uma concessão é escrita. |
| **Programação de atualização** | A propriedade, mais a fronteira da conta.                                                                                                                         |
| **Pergunta**                   | Apenas o proprietário. Uma pergunta não pode ser compartilhada por nenhum mecanismo.                                                                              |
| **Tarefa**                     | A ACL do *painel* a que pertence.                                                                                                                                 |
| **Conexão**                    | Seu proprietário e os administradores da conta. Qualquer Creator pode criar uma.                                                                                  |

**Uma lista vazia diz qual tipo de vazio é.** `GET /v1/access-rights/{folder}/{type}` carrega uma flag `enforced`, então `total: 0` para um tipo não concedível significa *nenhum direito desse tipo pode existir em lugar algum*, enquanto `total: 0` para `viz` significa *ninguém tem um*. Linhas escritas antes da recusa continuam listadas, e continuam removíveis, com `enforced: false` ao lado.

## Compartilhar um painel sem compartilhar seus dados não funciona[​](#compartilhar-um-painel-sem-compartilhar-seus-dados-não-funciona "Link direto para Compartilhar um painel sem compartilhar seus dados não funciona")

Esta é a pergunta que o modelo *não* responde do jeito que a maioria espera, então vale uma frase direta: **`viz:view` sozinho não dá a ninguém um painel que funcione.**

Os blocos de um painel buscam seus números com `POST /v1/query`, e essa rota autoriza **cada conjunto de dados participante pela ACL de conjuntos de dados**, no modo negar-em-vez-de-descartar: um único conjunto de dados que o leitor não pode ler rejeita a consulta inteira com `403 query_dataset_forbidden`. Ela nunca é executada sobre o subconjunto que ele *pode* ler. Portanto, "deixe o estagiário ver este painel, mas não os dados por trás" produz um painel cujos blocos falham, não um painel com os números ocultos.

Conceda também `dataset:view` na pasta que contém os conjuntos de dados — `viz:view` e `dataset:view` são um **par**, e uma concessão que carrega apenas o primeiro produz um painel em que cada bloco dá `403`. Se os conjuntos de dados não devem ser compartilhados em bloco, dê a eles uma pasta própria, do mesmo modo que a receita acima. Ou, se o objetivo for realmente mostrar um resultado sem entregar os dados subjacentes, **publique o painel ou incorpore-o**. Essas duas vias rodam sob a identidade de serviço da plataforma e nunca consultam esta ACL, que é exatamente o motivo pelo qual conseguem mostrar um número a alguém que não pode consultá-lo.

## Conferindo o próprio trabalho[​](#conferindo-o-próprio-trabalho "Link direto para Conferindo o próprio trabalho")

Cada campo acima descreve uma regra; nenhum deles diz com o que *uma pessoa específica* termina, e isso não se lê na lista de concessões. Os direitos se compõem — participações em grupos, um `deny` que vence todo allow, inclusive um herdado de um grupo, a separação por tipo de recurso e o controle de papel separado sobre mutações — em um resultado que nenhuma lista isolada mostra. O `deny` herdado é o caso que importa: ele nunca aparece na lista de allow que você lê, então "por que esta pessoa não vê isto" fica sem resposta só com a lista.

`GET /v1/access-rights/{folder}/effective?userId=` resolve. Para cada par (tipo de recurso, permissão) ele devolve o que a ACL decide, **qual regra decidiu**, se o papel da pessoa permite, se o tipo é aplicado, o produto disso tudo — e **a linha de direito de acesso que produziu o veredito**, que é o id a revogar quando a resposta é um deny. Seu agente tem a mesma coisa como `describe_effective_access`.

Ele responde apenas à pergunta da ACL, e diz isso numa lista `caveats` presente em toda resposta: não enxerga as linhas que a pessoa *possui* (a propriedade é por linha e vence a ACL da pasta), não sabe nada sobre as vias de painel público e de incorporação e — conforme a seção acima — um painel que funciona precisa também de direitos sobre seus conjuntos de dados. Você sempre pode perguntar sobre si mesmo; perguntar sobre outra pessoa exige a mesma posição que mudar a ACL: um admin da conta ou o dono da pasta.

## Alcançando isso a partir de código[​](#alcançando-isso-a-partir-de-código "Link direto para Alcançando isso a partir de código")

| Superfície                                              | Nos SDKs?                     |
| ------------------------------------------------------- | ----------------------------- |
| Grupos (criar, listar, atualizar, associação)           | **Sim** — `client.groups.*`   |
| Usuários (listar, atualizar, papel, associação a grupo) | **Sim** — `client.users.*`    |
| Conta                                                   | **Sim** — `client.accounts.*` |
| **Direitos de acesso (as concessões em si)**            | **Não**                       |

Direitos de acesso são excluídos da superfície gerada do SDK, então não há `client.accessRights` nem uma entrada para eles na **[Referência da API](/pt-BR/reference/vibedasher-sdk-api.md)**. As rotas existem e aceitam uma chave de API — elas simplesmente não fazem parte do contrato de cliente publicado e versionado, e preferimos te dizer isso do que deixar você construir contra um formato ao qual não nos comprometemos. Gerencie concessões no app, ou a partir do seu agente: o servidor MCP expõe ferramentas de conceder, revogar e listar direitos de acesso que chamam as mesmas rotas sob sua própria identidade, e herdam a mesma regra de admin-ou-proprietário-da-pasta.

## Filtragem por linha: a via de incorporação, e somente a via de incorporação[​](#row-level-filtering-the-embed-lane-and-only-the-embed-lane "Link direto para Filtragem por linha: a via de incorporação, e somente a via de incorporação")

Segurança por linha é real na Vibedasher, mas ela vive em um único lugar: **painéis incorporados atrás de um token assinado.**

Quando seu servidor gera um token de incorporação, ele pode anexar filtros à carga do token. Em toda consulta, a plataforma reescreve cada referência a conjunto de dados em uma subconsulta filtrada, então o visualizador fisicamente não consegue ler linhas fora da sua fatia. Os filtros vêm **exclusivamente** do token verificado — nunca do corpo da requisição — então um visualizador não pode ampliar seu próprio escopo editando uma requisição. Um filtro que nomeia uma coluna que não existe em nenhum esquema de conjunto de dados é rejeitado com um `403` em vez de silenciosamente ignorado, porque um filtro que discretamente não faz nada é uma brecha.

Três limites a considerar:

* **Aplica-se somente à via de incorporação.** A via de consulta do SDK *não tem* injeção obrigatória de filtro: uma chave de API é um chamador confiável do lado do servidor, e delimitar o escopo dos seus próprios usuários é trabalho seu, não da plataforma.
* **A ACL de pasta não é por linha.** Ela decide quais painéis e conjuntos de dados existem para você, não quais linhas dentro deles você pode ler. Essas são perguntas diferentes, e a Vibedasher as responde com mecanismos diferentes.
* **Um painel que consulta no navegador não pode ser delimitado de forma alguma, e isso é intencional.** Essa é a decisão a tomar antes de construir, não depois. Um painel auto-hospedado na via sem credenciais — a árvore ejetada cujo `dataLane` é `wasm`, ou qualquer coisa que você mesmo construiu sobre um extrato Parquet ou JSON — baixa os dados e executa toda consulta no navegador do visitante. O extrato é endereçado por **conjunto de dados e snapshot, não por consulta**, então ele contém todas as linhas daquele conjunto de dados, e todo visitante recebe o mesmo arquivo. Não sobra nenhum servidor no caminho para estreitá-lo: um filtro escrito na página muda o que é *desenhado*, nunca o que foi *entregue*.

O escopo por linha e a via sem credenciais se excluem mutuamente

Todo o apelo dessa via é que não há chave no bundle, nenhum token a gerar e nenhum backend seu para manter no ar — e o preço é exatamente a mesma propriedade vista do outro lado: nada pode entregar a um visualizador menos linhas do que a outro. Não é uma lacuna que será fechada; é o que significa colocar os dados no navegador.

Se alguns visualizadores precisam ver menos linhas, mantenha a consulta em um servidor. Dois formatos fazem isso, e ambos mantêm a credencial fora da página: **seu próprio backend gera um token de incorporação de curta duração carregando o filtro** (a plataforma acrescenta esse predicado a toda consulta que autoriza, antes de qualquer join ou agregação, de modo que a página não pode ampliá-lo), ou **código de servidor confiável consulta com uma chave de API** e retorna linhas que você já delimitou. Escolher primeiro a via rápida e "delimitá-la" depois com um filtro na página produz um painel que parece correto, não levanta nenhum erro, e entrega todas as linhas.

Veja **[Incorporação](/pt-BR/api/embedding-the-share-iframe.md)** para como um token é gerado e apresentado.

## Autenticação em duas etapas[​](#autenticação-em-duas-etapas "Link direto para Autenticação em duas etapas")

E-mail + senha é a única entrada no Vibedasher que depende de um único segredo. Os logins com Google e LinkedIn carregam o segundo fator do provedor; chaves de API e o token CLI do `vibedasher login` não são logins. Por isso o segundo fator é **opcional, por usuário, somente no login com senha**: um código TOTP de seis dígitos de qualquer aplicativo autenticador (Google Authenticator, 1Password, Authy…), ativado em **Configurações → Autenticação em duas etapas** escaneando um código QR e confirmando um código. Ao ativar, você recebe **oito códigos de recuperação de uso único**, exibidos uma só vez — um telefone perdido faz login com um deles.

O que muda no login: após uma senha correta, o navegador **não recebe sessão** — recebe um desafio de cinco minutos e propósito único que somente a etapa do código pode resgatar. A API nunca vê esse desafio como credencial: ele carrega uma audiência que a API recusa de imediato, então não pode ser reproduzido contra `api.*.vibedasher.com` nem colocado no cookie de sessão. Cinco códigos errados bloqueiam o desafio por quinze minutos; um código vale uma única vez.

Três limites para conhecer:

* **A ativação é individual.** Cada membro ativa para a própria conta; não é algo que um administrador liga para todo mundo.
* **Logins OAuth nunca recebem o desafio**, por decisão de projeto — colocar nosso fator em cima do fator do provedor é atrito sem segurança.
* **O desbloqueio é uma ação de administrador.** Um membro sem telefone e sem código de recuperação procura um **administrador da conta**, que redefine o segundo fator na página do membro (a ação é registrada). Um proprietário que é o único administrador, sem código de recuperação, passa pelo suporte.

## Uma configuração prática[​](#uma-configuração-prática "Link direto para Uma configuração prática")

Para uma equipe de dez pessoas com alguns conjuntos de dados sensíveis:

1. Torne **admins** a uma ou duas pessoas que administram a conta. Lembre-se de que elas são ingovernáveis por concessões — esse é o propósito do papel, e a razão para manter a lista curta.
2. Todo mundo que constrói é um **criador**; todo mundo que só lê é um **visualizador**. O papel de visualizador é uma recusa dura em mutações, então é uma fronteira real, não uma convenção de interface.
3. Coloque painéis e conjuntos de dados em pastas que correspondam a como você quer compartilhá-los — **uma pasta por público**, não por tópico. A pasta é a concessão.
4. Conceda a **grupos**, não a pessoas. A concessão então sobrevive à entrada ou saída de alguém.
5. Não crie pastas aninhadas esperando que a concessão da pasta pai as alcance. Conceda cada uma.
6. Se um painel precisa chegar a pessoas fora da conta, publique-o e incorpore-o com um token assinado carregando os filtros de linha delas — não dê a elas uma conta.
