Buenas prácticas en el diseño de APIs REST para asistentes conversacionales
23-09-2025
En el desarrollo de plataformas que integran chatbots inteligentes, uno de los retos recurrentes es definir una API clara, extensible y fácil de consumir. Más allá de la lógica del modelo de lenguaje, la interfaz que conecta el frontend con el backend debe estar bien estructurada: es la responsable de gestionar sesiones de chat, almacenar mensajes y coordinar llamadas al asistente.
Un debate frecuente surge en torno a la creación del primer chat: ¿qué verbo HTTP debe usarse cuando se necesita obtener el historial existente y, en caso de no existir, generar una nueva sesión con un mensaje de bienvenida? Esta decisión, aparentemente simple, tiene implicaciones importantes en consistencia, mantenibilidad y usabilidad de la API.
El dilema inicial: GET o POST
En muchos prototipos la lógica empieza con un endpoint único, algo como:
GET /chat-start
POST /chat-start
La idea es que, al invocarlo, la API devuelve los mensajes de la conversación si ya existe una sesión, o bien crea una nueva con el mensaje de bienvenida si no la hay.
Aunque sencillo en apariencia, este enfoque tiene varios problemas:
- Violación de semántica HTTP: un
GET
no debería tener efectos secundarios (como crear un recurso). - GET con body: algunos prototipos envían un
userId
en el cuerpo de un GET, algo no soportado por todas las librerías y considerado antipatrón. - Ambigüedad en responsabilidades: un endpoint que “lee o crea” puede confundir a consumidores y complicar testing.
La conclusión natural es separar claramente las operaciones de lectura y creación, o bien plantear un patrón de upsert controlado.
Principios para un diseño más limpio
Al analizar distintas alternativas, surgen algunas buenas prácticas a tener en cuenta:
- No usar verbos en los paths: evita rutas como
/chat-start
. En su lugar, utiliza sustantivos que representen recursos (/chats
,/messages
). - Separar lectura de escritura:
GET
debe ser idempotente y sin efectos secundarios;POST
para crear. - Usar mensajes de sistema para la bienvenida: el saludo inicial puede representarse como un mensaje más con
role: system
. - Versionado y extensibilidad: siempre incluir
/api/v1/
en las rutas para permitir evoluciones sin romper compatibilidad.
Un modelo REST más sólido
Partiendo de estas ideas, se puede definir un contrato de API más claro para asistentes de soporte técnico.
Crear o recuperar un chat activo
El frontend debe poder obtener un chat ya existente o iniciar uno nuevo con un único endpoint:
POST /api/v1/assistants/{assistantSlug}/chats
- Si el chat ya existe para ese usuario y ese asistente, devuelve
200 OK
con la sesión. - Si no existe, crea una nueva y devuelve
201 Created
con elLocation
del recurso. - En ambos casos, se devuelve el historial de mensajes (incluyendo el de bienvenida si se acaba de crear).
Este patrón de upsert simplifica el consumo desde el frontend.
Obtener mensajes del chat
GET /api/v1/chats/{chatId}/messages?limit=50&before=...&after=...
Devuelve los mensajes de la conversación, con soporte de paginación y cabeceras ETag
para caching eficiente.
Enviar mensajes del usuario
POST /api/v1/chats/{chatId}/messages
Crea un nuevo mensaje con role: user
y lo persiste en el historial.
Turnos completos con el asistente
Dado que en muchos casos no basta con almacenar un mensaje, sino que se requiere obtener una respuesta del modelo de lenguaje, conviene introducir un recurso adicional:
POST /api/v1/chats/{chatId}/turns
Este endpoint:
- Añade el mensaje del usuario,
- Invoca al asistente de soporte,
- Añade la respuesta (
role: assistant
) al historial, - Devuelve ambos mensajes en la respuesta.
Para escenarios en los que la respuesta puede tardar más o necesita streaming, también es posible un modelo asíncrono con colas de trabajo (/jobs/assistant-reply
) o Server-Sent Events.
Escalando a múltiples asistentes
En entornos reales no hay un único chatbot: un usuario puede interactuar con varios asistentes especializados (por ejemplo, soporte técnico, facturación o consultas generales).
Para ello es útil estructurar la API de la siguiente forma:
POST /api/v1/assistants/{assistantSlug}/chats
GET /api/v1/assistants/{assistantSlug}/chats/{chatId}/messages
POST /api/v1/assistants/{assistantSlug}/chats/{chatId}/turns
De este modo, cada asistente tiene su propio espacio de conversaciones, pero todos siguen el mismo contrato y patrón de uso.
Buenas prácticas adicionales
- Idempotencia: para operaciones de creación, soportar cabeceras como
Idempotency-Key
que permitan reintentos seguros desde el cliente. - Estados del chat: mantener un campo
status
(active
,archived
,closed
) y exponer unPATCH /chats/{chatId}
para gestionarlo. - Seguridad: idealmente el
userId
no debería viajar en el cuerpo de las peticiones; se infiere del token de autenticación. - Errores claros: usar códigos estándar (
404 Not Found
,409 Conflict
,422 Unprocessable Entity
) y respuestas en JSON concode
,message
yrequest_id
. - Documentación: publicar un contrato OpenAPI con ejemplos de cada endpoint, incluyendo códigos 200/201/404 y flujos de error.
Ejemplo de flujo completo
- El usuario accede a la web de soporte y el frontend invoca:
POST /api/v1/assistants/support/chats
Respuesta 201 Created
con el chat y mensaje de bienvenida (role: system
).
- El usuario pregunta:
POST /api/v1/chats/123/turns { "user_message": "¿Cómo reinstalo el software?" }
Respuesta 200 OK
con los mensajes del usuario y del asistente.
- El frontend refresca la conversación mostrando ambos.
Conclusión
Diseñar una API REST para asistentes conversacionales no se limita a elegir un verbo u otro: implica pensar en consistencia, extensibilidad y experiencia de desarrollador. Evitar endpoints ambiguos como /chat-start
, separar responsabilidades y apoyarse en convenciones REST ayuda a construir sistemas más robustos y fáciles de mantener.
En el caso de un entorno con múltiples asistentes —como diferentes áreas de soporte técnico— una estructura clara basada en /assistants/{assistantSlug}/chats
ofrece flexibilidad sin sacrificar simplicidad.
Como recordatorio final, la API es tan importante como el asistente mismo: de su diseño depende que la integración sea fluida, escalable y preparada para crecer con nuevas capacidades en el futuro.