Lo que se puede automatizar está aquí: clientes, personas, productos, notas de versión, base de conocimiento y agentes de IA. La especificación se genera del propio código — lo que está en la doc es lo que hace el servidor.
Tres pasos en el panel y una llamada en su terminal.
1
Genere la clave
En Integraciones › Claves de API, marcando solo los alcances que la integración necesita.
2
Guárdela fuera del código
Variable de entorno en su servidor. La clave de servidor nunca va al navegador.
3
Llame
Toda solicitud lleva Authorization: Bearer. Las escrituras aceptan Idempotency-Key.
ServidorPáginaSDKs
# la spec sale del servidor, no de un archivo escrito a mano
curl -s https://api.bfocus.com.br/api/v1/integration/openapi.json \
| jq '.info, (.paths | keys | length)'# toda llamada autenticada lleva la clave en la cabecera
curl -s https://api.bfocus.com.br/api/v1/integration/products \
-H "Authorization: Bearer $BFOCUS_API_KEY"
La especificación la sirve la propia API, generada del código
$ pip install bfocusimport os
from bfocus import sign_widget_identity_v2
# la firma lleva el instante en que se hizo y vale 7 días
firma = sign_widget_identity_v2(
os.environ["BFOCUS_WIDGET_SECRET"],
user_external_id="app-77", # sin ":"
customer_external_id="erp-1042",
)
# "v2.<ts>.<hex>" — hex = HMAC-SHA256 de "v2:<ts>:app-77:erp-1042"# Vencida, el widget le pide otra a la página (pestaña Renovación)
La identidad se firma en su servidor y vale 7 días
// la firma vale 7 días. Vencida, el widget le pide otra a la página// y sigue donde estaba — sin recargar, sin perder lo que se escribió
bFocus.init({
user: { externalId: "app-77" },
customer: { externalId: "erp-1042" },
userHash: sello,
// recibe { reason, identify }. Devuelva el sello nuevo: string, objeto,// promesa — o nada, y responda después con bFocus.identify
onIdentityExpired: () => fetch("/bfocus/sello").then((r) => r.text()),
});
// los otros dos caminos, si el callback no le sirve:
window.addEventListener("bfocus:identity-expired", () => {
bFocus.identify({ userHash: sello }); // campos parciales
});
// en el login, mande el usuario nuevo junto con el sello nuevo
bFocus.identify({ user: { externalId: "app-99" }, userHash: sello });
// bFocus.isOpen() y los avisos onOpen/onClose dicen cuándo reidentificar
La firma vencida se renueva sola; la página tiene 8 segundos para responder
$ npm install @bfocus/sdkimport { Bfocus } from"@bfocus/sdk";
const bf = new Bfocus({ apiKey: process.env.BFOCUS_API_KEY! });
// crea o actualiza por el código de SU sistema — volver a ejecutarlo no duplicaconst cliente = await bf.customers.upsert("ERP 1042", {
name: "Padaria Estrela",
email: "[email protected]",
});
Paquete @bfocus/sdk 0.2.5, publicado en npm
$ pip install bfocusimport os
from bfocus import Bfocus
bf = Bfocus(os.environ["BFOCUS_API_KEY"])
cliente = bf.customers.upsert("ERP 1042", name="Padaria Estrela")
print(cliente["id"])
Los siete están publicados en el registro de cada lenguaje, en la versión 0.2.5, con el código en un repositorio público. Para otro lenguaje, la API REST funciona igual.
Nueve paquetes de captura, el mismo evento en todos
Paquetes del adicional Monitoreo: una llamada a init con la clave de envío del agente y el error de su sistema llega agrupado, sin dato personal, y nace como tarea en Proyectos. La misma ruta, el mismo formato de evento y la misma versión en todos — ninguno tiene dependencia de runtime ni hace una llamada de red al importarse.
¿Sin build en el front? Una etiqueta de script alcanza: <script src="https://widget.bfocus.com.br/v1/monitor.js" data-bf-monitor-key="bf_mon_…" async></script>
Los nueve están en el registro de cada lenguaje, en la versión 0.1.2. Una falla del monitor nunca tira su sistema: el manejador que ya existía sigue valiendo, la cola es limitada y todo error nuestro se traga.
El papel llega desde su página, y usted lo sincroniza por la SDK
El papel dice quién es la persona en ese producto — administrador, facturación, mostrador. Su página declara role: { key, label } en el init del widget y, si ese papel aún no existe, queda registrado y vinculado solo. Estas tres operaciones son el otro camino: sincronizar, en cada deploy, los papeles que su sistema ya conoce, con las dos instrucciones que usa el agente de IA.
products.roles.listproducts:read
GET /products/{slug}/roles
Los papeles de ese producto, por nombre, con cuántas personas hay en cada uno.
products.roles.upsertproducts:write
PUT /products/{slug}/roles/{key}
Idempotente: puede correr en cada deploy. Solo cambia lo que viene en el cuerpo, y una cadena vacía limpia una instrucción.
products.roles.deleteproducts:write
DELETE /products/{slug}/roles/{key}
Quien tenía el papel queda sin papel y la novedad dirigida a él vuelve a valer para todos. Si su página sigue declarando la clave, el papel se registra de nuevo en la próxima sesión.
En los siete lenguajes, con el mismo nombre. La key es inmutable — es por ella que su página se refiere al papel; el rótulo y las dos instrucciones cambian a voluntad. El papel es declaración, no permiso: lo declara su página y viaja FUERA de la firma (el userHash cubre usuario y cliente), así que las limitaciones de acceso son instrucción de conversación para el agente — quien decide lo que la persona ve sigue siendo bFocus.
Nadie escribe un cliente dos veces. Su ERP, su SaaS o su sistema de escritorio manda lo que ya tiene — con el id que ya usa — y bFocus pasa a saber quién es quién, quién usa qué producto y qué salió en cada versión.
1
El id es el suyo
PUT /customers/{su_id}: crear y actualizar son la misma llamada, con el código que su sistema ya usa. Vale para cliente, persona, contacto, identificador extra, vínculo con producto, producto, nota de versión y artículo de la base.
2
La carga inicial va en lote
Hasta 500 clientes o 500 personas por llamada (100 artículos). Cada ítem es independiente: lo que falla vuelve con su propio código de error, en el mismo orden, y no tumba a los demás.
3
Repetir es seguro
Las escrituras de la API pública aceptan Idempotency-Key: por 24 horas, la repetición devuelve la respuesta original con Idempotent-Replayed, sin ejecutar de nuevo. Las SDK mandan la clave solas en cada reintento — un timeout seguido de retry devuelve la misma respuesta en vez de crear el registro dos veces.
4
El registro responde qué pasó
linked cuando la misma persona pasa a valer en un cliente más, merged_into cuando su id se volvió alias de una ficha que ya existía, unlinked cuando sale de este cliente y sigue en los otros. El 409 de contacto repetido dice de quién es el contacto.
Qué conecta la sincronía del otro lado
Quien usted sincronizó es quien entra en el widget, con acceso decidido por vínculo y la firma de su servidor
El vínculo cliente↔producto decide qué ve cada uno de las novedades y qué usa la IA para responder
La nota de versión publicada por su deploy, idempotente por producto y versión, enciende el distintivo en la app del cliente y registra el acuse de cada uno
Los artículos de la base entran por código externo y se vuelven fuente del agente de IA, con búsqueda por la misma API
El historial del cliente acepta las interacciones de su sistema, y aparecen en el dossier al lado de la Mesa
El CPF une registros solo; sin CPF, nombre y contacto van a la cola de posibles duplicados en vez de fusionarse a la fuerza
Qué no hace la API
No abre tickets: el ticket nace del widget, del correo, del chat, del teléfono o de WhatsApp — y es a propósito, para que toda atención tenga una conversación de origen.
No crea leads: el lead nace en la puerta de su sitio o a mano, en la pantalla de Leads.
Lo que cubre la API
Pocos recursos, bien resueltos
Preferimos una API pequeña que no cambia a una API grande que se rompe.
Clientes
El contratante, empresa o persona física: kind, el documento fiscal en document, razón social e inscripciones en la empresa, documento de identidad en la persona física. Con contactos, productos vinculados, campos personalizados e historial de contacto.
Personas
La persona vive dentro del cliente (la carga en lote y sus identificadores tienen ruta propia), con documento, campos personalizados y acceso al portal. La respuesta dice qué pasó: linked cuando la misma persona pasa a valer en un cliente más, merged_into cuando su id se volvió alias de una ficha que ya existía, unlinked cuando sale de este cliente y sigue en los demás.
Identificadores extra
Una persona o un cliente se encuentra por varios ids. La lista vuelve legible — external_id, label y quién lo vinculó — así descubre que el id de su sistema se volvió alias del registro de otro.
Carga en lote
Hasta 500 clientes o personas en una llamada. Cada ítem es independiente: el que falla vuelve con su código de error, en el mismo orden, y no tumba a los demás.
Productos
El catálogo de lo que vende su empresa, que organiza base de conocimiento, agentes y notas de versión.
Papeles del producto
Listar, registrar/actualizar y borrar el papel de quien usa cada producto: GET, PUT y DELETE en /products/{slug}/roles, con los alcances products:read y products:write. El PUT es idempotente, hecho para correr en su deploy. El papel también llega solo por el widget — y sirve para guiar al agente de IA y dirigir novedades internas, nunca como permiso.
Notas de versión
Publicación idempotente por producto y versión, pensada para correr dentro de su deploy.
Base de conocimiento
Creación y actualización por código externo, en lotes de hasta 100, con publicación y búsqueda.
Agentes de IA
Lectura de la configuración y vista previa de respuesta, para que pruebe el agente antes de liberarlo.
Claves
Varias por empresa, con alcance por recurso, lista de IPs opcional, revocación y registro del último uso. La dirección rechazada entra en la fila de Autorizaciones, para liberarla con un clic.
Reglas de la casa
Autenticación por Bearer, con alcances separados de lectura y escritura
Idempotency-Key en las escrituras de esta API: por 24 horas, repetir la llamada devuelve la misma respuesta en vez de crearla de nuevo
X-Request-Id en toda respuesta, para que la correlacione con su log
Límite de 600 solicitudes por minuto por clave
De servidor a servidor: la API de integración no valida dominios ni usa CORS — quien autentica es la clave secreta
Restricción de origen opcional por IP, rango CIDR o nombre de máquina resuelto en el momento de la llamada
Errores con código estable, pensados para que su tratamiento no se rompa cuando cambie el texto
Borrar un contacto es explícito: clear: ["email", "phone"] lo borra; null o el campo ausente significa "no lo toque"
El 409 de contacto ya usado dice de quién es el contacto, así que usted decide sin abrir la base
Especificación OpenAPI con 42 operaciones, generada del código y servida por la propia API
Identidad verificada
Su servidor dice quién está del otro lado
Con la identidad verificada activada, es lo que impide que un cliente vea el ticket de otro. Vale en el widget web, en los paquetes nativos y en el modo navegador. Quien crea el widget hoy ya empieza con ella activada.
1 Su backend firma el par usuario + empresa con la clave secreta de su cuenta
2 La app pasa esa firma al inicializar el widget
3 bFocus verifica la firma. Con la identidad verificada activada, bFocus rechaza a quien no trae una firma válida
4 La firma vale 7 días y lleva el instante en que se hizo. Cuando vence, el widget le pide una nueva a la página y sigue donde estaba, sin recargar — el visitante no pierde lo que escribió
5 La página responde por uno de estos caminos: onIdentityExpired en las opciones del init, el evento bfocus:identity-expired en window, o bFocus.identify({ user, customer, userHash }) en cualquier momento, con los campos que quiera
6 La respuesta tiene que llegar en 8 segundos. Sin ella, aparece la pantalla de identidad rechazada, como antes
7 En los nueve paquetes nativos no hay nada que hacer: la renovación usa el userHashProvider que usted ya configuró
8 Al cambiar la clave secreta, la anterior sigue valiendo 7 días, así que el cambio no deja fuera a quien lleva la firma anterior
Ejemplos listos
La guía de identidad verificada trae el cálculo de la firma v2 en Node, Python, PHP y en los demás lenguajes de las SDKs, con un ejemplo para copiar y una prueba para comprobar que su implementación coincide con la nuestra.
Preguntas técnicas
¿Por dónde entran los tickets?
Por el widget, con la clave publicable, por el correo y por WhatsApp. La API de integración se ocupa del registro alrededor: clientes, contactos, productos, identificadores, importación en lote e interacciones con clientes.
¿Puedo ver el código de los SDKs?
Puede. Cada SDK y cada paquete nativo tiene repositorio público en github.com/bernisoftware, con el README y los ejemplos que aparecen en esta página.
¿Cómo versionan la API?
Cada release de las SDKs declara si la superficie pública cambió. Recomendamos fijar la versión exacta del paquete y subir leyendo esa línea.
¿Puedo usar solo la API, sin widget?
Puede. Mucha gente empieza sincronizando clientes del ERP y publicando notas de versión desde el deploy, y solo después coloca el widget.
¿Cómo decide el widget en qué dominios abre?
El navegador informa la dirección de la página, así que la comprobación va por el dominio real — esa lista es lo que protege la clave publicable, que es pública. Registre el dominio raíz y los subdominios vienen incluidos: https://cliente.com ya habilita app.cliente.com y tienda.cliente.com. Es el camino recomendado, porque deja menos margen de error. El comodín explícito también funciona, como alternativa: https://*.cliente.com. El esquema tiene que coincidir (https no cubre http) y el puerto también, cuando usted declara uno. En los paquetes nativos el origen es app:// seguido del id de la aplicación, y ahí el host es un identificador: app://com.cliente no habilita app://com.cliente.otro, así que registre cada id.
Un dominio (o una IP) fue rechazado. ¿Cómo lo libero?
Sin escribir nada. Un dominio rechazado en el widget, o una dirección rechazada en una clave de API, se vuelve una línea en Empresa › Integraciones › Autorizaciones: la dirección exactamente como llegó, cuántos intentos y cuándo fue el último. Liberar es un clic, y el valor entra en la lista correcta — los dominios del widget o las IPs de esa clave. Eso acaba con el error de escribir www en lugar del dominio raíz, o http en lugar de https. Solo el administrador libera. Descartar saca la línea de la fila sin autorizar nada.
La firma venció con la página abierta. ¿El usuario pierde lo que escribió?
No. El widget le pide una identidad nueva a la página y sigue donde estaba, sin recargar. La página responde por el onIdentityExpired del init, por el evento bfocus:identity-expired en window o llamando a bFocus.identify en cualquier momento — y la respuesta puede ser una promesa. El plazo es de 8 segundos; pasado eso, aparece la pantalla de identidad rechazada. En los paquetes nativos la renovación usa el userHashProvider que usted ya configuró.
¿Puedo restringir la API de integración por dominio?
No, y es a propósito: en una llamada de servidor no existe Origin, y un dominio declarado por el cliente sería falsificable — parecería protección sin serlo. La credencial aquí es la clave secreta, con alcances por recurso, límite por minuto, revocación y registro del último uso. Existe además la restricción opcional por dirección de origen: IP, rango CIDR o nombre de máquina resuelto por DNS en el momento de la llamada. Le sirve a quien llama desde un servidor con IP de salida fija, como un VPS o una máquina propia. En una plataforma sin IP de salida fija — App Platform, Lambda, Cloud Run, CI — no sirve, porque la IP que sale de ahí no tiene relación con el dominio de su aplicación. La clave secreta nunca va al navegador. Si su front necesita los datos, quien llama a la API es el backend de su empresa; al navegador va solo la clave publicable del widget.
Genere una clave y pruébela en diez minutos
La cuenta de prueba ya viene con acceso a la API y a la especificación completa.