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
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 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.
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
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 —
datasetouviz. São os dois que uma concessão pode nomear; veja O que uma concessão pode nomear 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
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. 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
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
Em ordem. A primeira regra que corresponder vence:
- Admin → permite. Incondicionalmente, antes que qualquer concessão seja lida.
- 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. - Propriedade → permite. O usuário que é proprietário da linha sempre pode lê-la e alterá-la.
- 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
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
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
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
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
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
| 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. 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
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.
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 para como um token é gerado e apresentado.
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
Para uma equipe de dez pessoas com alguns conjuntos de dados sensíveis:
- 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.
- 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.
- 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.
- Conceda a grupos, não a pessoas. A concessão então sobrevive à entrada ou saída de alguém.
- Não crie pastas aninhadas esperando que a concessão da pasta pai as alcance. Conceda cada uma.
- 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.