Dois papéis, e nenhum terceiro
owner e member. Esse é o catálogo inteiro.
Aqui embaixo não existe catálogo de permissões nem forma de definir um papel.
Isso é deliberado: papéis e permissões
existem um plano acima porque a sua organização define os dela, e um time B2B
dentro do seu produto quer quem administra e todo o resto. Papéis customizados
chegam quando alguém pedir.
Dois invariantes vêm com os papéis, e os dois espelham o plano de staff:
- Um customer nunca perde seu último owner. Rebaixá-lo, removê-lo ou ele
sair responde
409 last_owner. Promova outra pessoa antes. - Você não pode trocar o seu próprio papel. É assim que alguém se dá tudo.
O customer ativo
Um contato pode pertencer a vários customers, então toda chamada diz dentro de qual ela está agindo — no headerX-Customer-Id.
active_customer_id guardado na sessão.
Um id de customer que alguém cole não casa com nenhuma linha de vínculo e
responde
404 — o mesmo 404 de um customer que não existe. Revogar um
vínculo vale na próxima requisição, não no próximo login.Não existe um cursor compartilhado para uma aba mover debaixo da outra.
As claims seguem o que esta chamada pediu, e o próximo mint está a no máximo
cinco minutos de distância.
GET /v1/contact/customers:
O time dentro do JWT
POST /v1/contact/token coloca o customer ativo no
JWT de sessão, para o seu backend
responder “qual time, e essa pessoa pode administrá-lo” offline:
X-Customer-Id no mint e as claims seguem. Nomear um customer ao qual o
contato não pertence é recusado com 404 em vez de simplesmente omitir a claim
— um token sem o campo que o seu backend usa como porta é pior que um erro.
As duas claims ficam ausentes quando o contato não pertence a customer nenhum.
Trate ausente como “sem time”, nunca como “sem necessidade de checar”.
Abrindo um time
identity_required).
O seu próprio servidor também pode criar customers direto, que é o formato usual
no modo federado — lá o seu produto já sabe
quem trabalha com quem.
Convites
A resposta não diz nada sobre o endereço já ter conta com você. O único conflito
que ela reporta —
already_member — é participação no time de quem chamou, que
essa pessoa já lê na lista do time. Todo outro caso segue o caminho idêntico e
produz a resposta idêntica.POST /v1/contact/customer/invitations/{id}/resend rotaciona o hash e a
validade, então o link da mensagem anterior para de funcionar: um convite é uma
credencial, e nunca pode haver duas vivas para o mesmo assento. Revogar
(DELETE …/{id}) fecha o assento de vez.
Aceitar exige duas provas
POST /v1/contact/invitations/accept precisa de um contato autenticado e do
token.
O token prova o convite — que este assento foi oferecido a este endereço. A
sessão prova a pessoa. Nenhum dos dois basta sozinho: um link encaminhado
sentaria quem o abrisse.
Por isso o endereço convidado precisa ser uma identidade do contato que
aceita, ou a chamada responde 403 invitation_not_yours. É a mesma regra que
todo fluxo por endereço deste plano já segue: um e-mail é um atributo até algo
transformá-lo em aresta de identidade,
e um atributo é algo que qualquer um digita.
Um endereço vira aresta pelo cadastro hospedado, por um magic link, por um
código de seis dígitos, por um login social em que o provedor atestou o
endereço, ou pelo seu próprio servidor chamando POST /v1/contacts com ele.
Passar um endereço para /v1/boot no modo federado deliberadamente não faz
isso.
Eventos
Toda mutação publica um fato que você consome pelos eventos:
Payloads carregam ids, nunca endereços — o e-mail fica fora de toda fila e todo
log por onde o envelope passa.