Buenas prácticas en el diseño de APIs REST para asistentes conversacionales

23-09-2025

Por Cristian Manuel Suarez Vera

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:

  1. No usar verbos en los paths: evita rutas como /chat-start. En su lugar, utiliza sustantivos que representen recursos (/chats, /messages).
  2. Separar lectura de escritura: GET debe ser idempotente y sin efectos secundarios; POST para crear.
  3. Usar mensajes de sistema para la bienvenida: el saludo inicial puede representarse como un mensaje más con role: system.
  4. 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 el Location 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 un PATCH /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 con code, message y request_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

  1. 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).

  1. 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.

  1. 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.