Clientes
O contratante, PJ ou PF: kind, CNPJ ou CPF em document, razão social e inscrições na empresa, RG na pessoa física. Com contatos, produtos vinculados, campos personalizados e registros de contato.
O que dá para automatizar está aqui: clientes, pessoas, produtos, notas de versão, base de conhecimento e agentes de IA. A especificação é gerada do próprio código — o que está na doc é o que o servidor faz.
Comece pelo que a sua integração faz: falar com a API a partir do servidor ou colocar o widget dentro do app.
A API pública em OpenAPI, gerada do código: rotas, escopos, erros com código estável e idempotência.
Abrir openapi.jsonSete SDKs oficiais, cada uma com README, exemplos e código aberto no GitHub.
Ver as sete SDKsNove pacotes para apps — Flutter, iOS, Android, React Native, .NET, Java, Python, Delphi e Lazarus — com o comando de instalação de cada um.
Ver os apps nativosO snippet para o seu site ou sistema web, que também serve Electron, Tauri, Capacitor e Ionic.
Ver o widget webNove pacotes que mandam o erro do seu sistema para o bFocus — navegador, Node, Python, PHP, .NET, Java, Go, React Native e Flutter — com o comando de instalação de cada um.
Ver os nove pacotesO papel de quem usa o seu produto chega pelo role do widget e se sincroniza pelas SDKs — e é declaração, não permissão.
Ver as três operaçõesComo o seu servidor assina usuário e empresa para um cliente nunca ver o chamado de outro — e como a assinatura se renova sozinha.
Ver como assinarTrês passos no painel e uma chamada no seu terminal.
Em Integrações › Chaves de API, marcando só os escopos de que a integração precisa.
Variável de ambiente no seu servidor. A chave de servidor nunca vai para o navegador.
Toda requisição leva Authorization: Bearer. As escritas aceitam Idempotency-Key.
# a spec sai do servidor, não de um arquivo escrito à mão curl -s https://api.bfocus.com.br/api/v1/integration/openapi.json \ | jq '.info, (.paths | keys | length)' # toda chamada autenticada leva a chave no cabeçalho curl -s https://api.bfocus.com.br/api/v1/integration/products \ -H "Authorization: Bearer $BFOCUS_API_KEY"
A especificação é servida pela própria API, gerada do código
$ pip install bfocus import os from bfocus import sign_widget_identity_v2 # a assinatura carrega o instante em que foi feita e vale 7 dias assinatura = sign_widget_identity_v2( os.environ["BFOCUS_WIDGET_SECRET"], user_external_id="app-77", # sem ":" customer_external_id="erp-1042", ) # "v2.<ts>.<hex>" — hex = HMAC-SHA256 de "v2:<ts>:app-77:erp-1042" # Vencida, o widget pede outra à página (aba Renovação)
A identidade é assinada no seu servidor e vale 7 dias
// a assinatura vale 7 dias. Vencida, o widget pede outra à página // e continua de onde estava — sem recarregar, sem perder o que foi digitado bFocus.init({ user: { externalId: "app-77" }, customer: { externalId: "erp-1042" }, userHash: selo, // recebe { reason, identify }. Devolva o selo novo: string, objeto, // promessa — ou nada, e responda depois com bFocus.identify onIdentityExpired: () => fetch("/bfocus/selo").then((r) => r.text()), }); // os outros dois caminhos, se o callback não servir: window.addEventListener("bfocus:identity-expired", () => { bFocus.identify({ userHash: selo }); // campos parciais }); // no login, mande o usuário novo junto com o selo novo bFocus.identify({ user: { externalId: "app-99" }, userHash: selo }); // bFocus.isOpen() e os avisos onOpen/onClose dizem a hora certa de reidentificar
A assinatura vencida se renova sozinha; a página tem 8 segundos para responder
$ npm install @bfocus/sdk import { Bfocus } from "@bfocus/sdk"; const bf = new Bfocus({ apiKey: process.env.BFOCUS_API_KEY! }); // cria ou atualiza pelo código do SEU sistema — rodar de novo não duplica const cliente = await bf.customers.upsert("ERP 1042", { name: "Padaria Estrela", email: "[email protected]", });
Pacote @bfocus/sdk 0.2.5, publicado no npm
$ pip install bfocus import os from bfocus import Bfocus bf = Bfocus(os.environ["BFOCUS_API_KEY"]) cliente = bf.customers.upsert("ERP 1042", name="Padaria Estrela") print(cliente["id"])
Pacote bfocus 0.2.5, publicado no PyPI
$ composer require bfocus/bfocus <?php require __DIR__ . '/vendor/autoload.php'; $bf = new \Bfocus\Bfocus(getenv('BFOCUS_API_KEY')); $cliente = $bf->customers->upsert('ERP 1042', [ 'name' => 'Padaria Estrela', ]);
Pacote bfocus/bfocus 0.2.5, publicado no Packagist
Todas passam pelo mesmo conjunto de 74 casos de conformidade: mesmos nomes, mesmos erros com código estável e a mesma política de nova tentativa.
npm install @bfocus/sdk README e código no GitHub pip install bfocus README e código no GitHub composer require bfocus/bfocus README e código no GitHub dotnet add package Bfocus README e código no GitHub implementation("br.com.bernisoftware:bfocus:0.2.5") README e código no GitHub go get github.com/bernisoftware/bfocus-go README e código no GitHub gem install bfocus README e código no GitHub As sete estão publicadas no registro de cada linguagem, na versão 0.2.5, com o código em repositório público. Para outra linguagem, a API REST atende do mesmo jeito.
Pacotes do módulo Monitoramento (adicional): uma chamada de init com a chave de envio do agente e o erro do seu sistema chega agrupado, sem dado pessoal, e nasce como demanda no Projetos. Mesma rota, mesmo formato de evento e a mesma versão em todos — nenhum tem dependência de runtime e nenhum faz chamada de rede ao ser importado.
npm install @bfocus/monitor README e código no GitHub npm install @bfocus/monitor-node README e código no GitHub npm install @bfocus/monitor-react-native README e código no GitHub pip install bfocus-monitor README e código no GitHub composer require bfocus/monitor README e código no GitHub dotnet add package Bfocus.Monitor README e código no GitHub implementation("br.com.bernisoftware:bfocus-monitor:0.1.2") README e código no GitHub go get github.com/bernisoftware/[email protected] README e código no GitHub flutter pub add bfocus_monitor README e código no GitHub Sem build no front? Uma tag de script resolve: <script src="https://widget.bfocus.com.br/v1/monitor.js" data-bf-monitor-key="bf_mon_…" async></script>
Os nove estão no registro de cada linguagem, na versão 0.1.2. Falha do monitor nunca derruba o seu sistema: o tratador que já existe continua valendo, a fila é limitada e todo erro nosso é engolido.
O papel diz quem é a pessoa naquele produto — administrador, faturamento, balconista. A sua página declara role: { key, label } no init do widget e, se aquele papel ainda não existir, ele é cadastrado e vinculado sozinho. Estas três operações são o outro caminho: sincronizar, a cada deploy, os papéis que o seu sistema já conhece, com as duas instruções que o agente de IA usa.
GET /products/{slug}/roles Os papéis daquele produto, em ordem de nome, com quantas pessoas estão em cada um.
PUT /products/{slug}/roles/{key} Idempotente: pode rodar a cada deploy. Só o que vem no corpo muda, e string vazia limpa uma instrução.
DELETE /products/{slug}/roles/{key} Quem estava no papel fica sem papel e a novidade dirigida a ele volta a valer para todos. Se a sua página continuar declarando a chave, ele é cadastrado de novo na próxima sessão.
Nas sete linguagens, com o mesmo nome. A key é imutável — é por ela que a sua página se refere ao papel; o rótulo e as duas instruções mudam à vontade. O papel é declaração, não permissão: ele é declarado pela sua página e viaja FORA da assinatura (o userHash cobre usuário e cliente), então as limitações de acesso são instrução de conversa para o agente — quem decide o que a pessoa vê continua sendo o bFocus.
Ninguém digita cliente duas vezes. O seu ERP, o seu SaaS ou o seu sistema de mesa manda o que já tem — com o id que ele já usa — e o bFocus passa a saber quem é quem, quem usa qual produto e o que saiu em cada versão.
PUT /customers/{seu_id}: criar e atualizar são a mesma chamada, com o código que o seu sistema já usa. Vale para cliente, pessoa, contato, identificador extra, vínculo com produto, produto, nota de versão e artigo da base.
Até 500 clientes ou 500 pessoas por chamada (100 artigos). Cada item é independente: o que falha volta com o código do erro dele, na mesma ordem, e não derruba os outros.
As escritas da API pública aceitam Idempotency-Key: por 24 horas, a repetição devolve a resposta original com Idempotent-Replayed, sem executar de novo. As SDKs mandam a chave sozinhas em cada nova tentativa — um timeout seguido de retry devolve a mesma resposta em vez de criar o registro duas vezes.
linked quando a mesma pessoa passa a valer em mais um cliente, merged_into quando o seu id virou apelido de uma ficha que já existia, unlinked quando ela sai daqui e continua nos outros. O 409 de contato repetido diz de quem é o contato.
O que a sincronia liga do outro lado
O que a API não faz
Preferimos uma API pequena que não muda a uma API grande que quebra.
O contratante, PJ ou PF: kind, CNPJ ou CPF em document, razão social e inscrições na empresa, RG na pessoa física. Com contatos, produtos vinculados, campos personalizados e registros de contato.
A pessoa vive dentro do cliente (e a carga em lote e os identificadores dela têm rota própria), com CPF, campos personalizados e acesso ao portal. A resposta diz o que aconteceu: linked quando a mesma pessoa passa a valer em mais um cliente, merged_into quando o seu id virou apelido de uma ficha que já existia, unlinked quando ela sai deste cliente e continua nos outros.
Uma pessoa ou um cliente é achado por vários ids. A lista volta legível — external_id, label e quem ligou — então você descobre que o id do seu sistema virou apelido do cadastro de outro.
Até 500 clientes ou pessoas numa chamada. Cada item é independente: o que falha volta com o código do erro dele, na mesma ordem, e não derruba os outros.
O catálogo do que a sua empresa vende, que organiza base de conhecimento, agentes e notas de versão.
Listar, cadastrar/atualizar e apagar o papel de quem usa cada produto: GET, PUT e DELETE em /products/{slug}/roles, com os escopos products:read e products:write. O PUT é idempotente, feito para rodar no seu deploy. O papel também chega sozinho pelo widget — e serve para guiar o agente de IA e dirigir novidade interna, nunca como permissão.
Publicação idempotente por produto e versão, pensada para rodar dentro do seu deploy.
Criação e atualização por código externo, em lotes de até 100, com publicação e busca.
Leitura da configuração e prévia de resposta, para você testar o agente antes de liberar.
Várias por empresa, com escopo por recurso, lista de IPs opcional, revogação e registro do último uso. O endereço recusado entra na fila de Autorizações, para liberar com um clique.
Regras da casa
Com a identidade verificada ligada, é o que impede um cliente de ver o chamado de outro. Vale no widget web, nos pacotes nativos e no modo navegador. Quem cria o widget hoje já começa com ela ligada.
O guia de identidade verificada traz o cálculo da assinatura v2 em Node, Python, PHP e nas demais linguagens das SDKs, com um exemplo para copiar e um teste para conferir se a sua implementação bate com a nossa.
Pelo widget, com a chave publicável, pelo e-mail e pelo WhatsApp. A API de integração cuida do cadastro em volta: clientes, contatos, produtos, identificadores, importação em lote e interações com clientes.
Pode. Cada SDK e cada pacote nativo tem repositório público em github.com/bernisoftware, com o README e os exemplos que aparecem nesta página.
Cada release das SDKs declara se a superfície pública mudou. Recomendamos fixar a versão exata do pacote e subir lendo essa linha.
Pode. Muita gente começa sincronizando clientes do ERP e publicando notas de versão pelo deploy, e só depois coloca o widget.
O navegador informa o endereço da página, então a conferência é por domínio de verdade — é essa lista que protege a chave publicável, que é pública. Cadastre o domínio raiz e os subdomínios vêm junto: https://cliente.com.br já libera app.cliente.com.br e loja.cliente.com.br. É o caminho recomendado, porque dá menos margem a erro. O curinga explícito também funciona, como alternativa: https://*.cliente.com. O esquema precisa bater (https não cobre http) e a porta também, quando você declara uma. Nos pacotes nativos a origem é app:// seguido do id do aplicativo, e ali o host é um identificador: app://com.cliente não libera app://com.cliente.outro, então cadastre cada id.
Sem digitar nada. Domínio recusado no widget, ou endereço recusado numa chave de API, vira uma linha em Empresa › Integrações › Autorizações: o endereço exatamente como chegou, quantas tentativas e quando foi a última. Liberar é um clique, e o valor entra na lista certa — os domínios do widget ou os IPs daquela chave. Isso acaba com o erro de digitar www no lugar do domínio raiz, ou http no lugar de https. Só administrador libera. Descartar tira da fila sem autorizar nada.
Não. O widget pede uma identidade nova à página e segue de onde estava, sem recarregar. A página responde pelo onIdentityExpired do init, pelo evento bfocus:identity-expired em window ou chamando bFocus.identify a qualquer momento — e a resposta pode ser uma promessa. O prazo é de 8 segundos; passando disso, aparece a tela de identidade recusada. Nos pacotes nativos a renovação usa o userHashProvider que você já configurou.
Não, e isso é de propósito: numa chamada de servidor não existe Origin, e um domínio declarado pelo cliente seria forjável — pareceria proteção sem ser. A credencial ali é a chave secreta, com escopos por recurso, limite por minuto, revogação e registro do último uso. Existe ainda a restrição opcional por endereço de origem: IP, faixa CIDR ou nome de máquina resolvido por DNS na hora da chamada. Ela resolve para quem chama de servidor com IP de saída fixo, como VPS ou servidor próprio. Em plataforma sem IP de saída fixo — App Platform, Lambda, Cloud Run, CI — ela não serve, porque o IP que sai de lá não tem relação com o domínio do seu app. A chave secreta nunca vai para o navegador. Se o seu front precisa dos dados, quem chama a API é o backend da sua empresa; no navegador vai só a chave publicável do widget.
A conta de teste já vem com acesso à API e à especificação completa.