Si la API devuelve 400 con role 'system' is not supported on this model, no empieces rotando la clave, subiendo cuota o añadiendo reintentos. La solicitud llegó a una ruta que valida la estructura del mensaje, y esa ruta rechazó el valor final de messages[n].role. Es un choque de contrato de roles, no un problema genérico de autenticación o red.
El primer paso es leer el param del cuerpo de error y compararlo con el JSON final que salió después del SDK, framework, middleware, deployment de Azure o gateway compatible. La reparación debe conservar la intención de la instrucción del sistema y moverla a la superficie de instrucciones de mayor prioridad que acepte esa misma ruta.
| Pista de fallo | Dueño probable | Primer movimiento seguro | Verificación |
|---|---|---|---|
messages[n].role = system | Endpoint, gateway, Azure deployment o adapter contract | Conservar la instrucción y mapearla a la instruction surface admitida por esa ruta | Una solicitud mínima en la misma ruta ya no falla por role |
Se rechaza developer | API version, compatibility layer o framework route | Confirmar si esa ruta de Chat Completions acepta el rol; si no, usar Responses instructions o un mapping específico | La misma forma de payload pasa en la ruta seleccionada |
Falla tool o function legacy | Protocolo de tools incompatible | Usar el tool message y call id que documenta la ruta | El tool result se acepta sin role error |
| El rol no está en tu código | Agent framework, prompt wrapper, middleware o SDK adapter | Registrar el outbound JSON final y desactivar o remapear el mensaje inyectado | El repro mínimo y el flujo completo usan el mismo role set aceptado |
Detente aquí antes de cambiar de proveedor, aumentar timeouts o tocar la cuota. Nada de eso corrige un contrato de roles inválido. La prueba real es una solicitud mínima contra el mismo provider, base URL, endpoint, API version, model o deployment, SDK adapter y framework route.
Lee el cuerpo de error antes de tocar el código
La parte útil de este 400 normalmente no es la frase en inglés, sino el objeto estructurado. Guarda status, type, code, message y, sobre todo, param. Si apunta a messages[0].role, mira el primer objeto del array. Si apunta a otro índice, revisa mensajes añadidos por history, tools, adapters o middleware.
No basta con mirar el array que escribiste en tu handler. Antes del envío, un memory prompt, default system prompt, guardrail middleware, retry wrapper, adapter OpenAI-compatible o gateway puede transformar el payload. Registra la forma final, desinfectada, lo más cerca posible del HTTP boundary.
{
"endpoint": "POST /v1/chat/completions",
"model": "example-model",
"messages": [
{ "role": "system", "content": "[redacted instruction]" },
{ "role": "user", "content": "[redacted user task]" }
]
}
Ese log solo debe probar estructura. No registres API keys, bearer tokens, cookies, prompts privados, texto de usuarios, contenido de archivos ni salidas de tools. Lo que necesitas es endpoint, model o deployment, índice del mensaje, rol y capa que construyó el payload final.

Repara por endpoint, no por costumbre
La trampa principal es creer que hay una sustitución universal. system no siempre se reemplaza por developer, y mover instrucciones a user no es una reparación neutral. El soporte de roles depende del endpoint, API version, Azure deployment, provider gateway, SDK y a veces del framework de agentes.
Al 21 de mayo de 2026, OpenAI Responses tiene una superficie superior instructions para instrucciones de desarrollador. Las rutas oficiales actuales de Chat Completions usan messages y pueden aceptar roles de tipo instrucción en esas rutas. Eso no prueba que un gateway compatible, un deployment de Azure, un adapter antiguo o un alias de modelo de otro proveedor acepte el mismo contrato.
Si estás en una ruta oficial de Chat Completions que acepta developer, el cambio puede ser:
{
"model": "your-selected-model",
"messages": [
{ "role": "developer", "content": "Follow the support triage rules." },
{ "role": "user", "content": "Classify this API error." }
]
}
Si estás en Responses, conserva la instrucción persistente en instructions y envía la tarea del usuario por input:
{
"model": "your-selected-model",
"instructions": "Follow the support triage rules.",
"input": "Classify this API error."
}
Si un gateway solo acepta user y assistant, no metas inmediatamente las instrucciones del sistema dentro de un user message. Busca primero un instruction field, model option, adapter setting o ruta alternativa. Si no existe, documenta que el fallback cambia la prioridad de instrucciones.

Cada rol tiene una rama de reparación
Cuando se rechaza system, el objetivo es conservar la fuerza de la instrucción. En Chat Completions oficial puede ser developer; en Responses suele ser instructions; en un gateway puede ser un campo propio. Bajar a un user message es último recurso y debe tratarse como cambio de comportamiento.
Cuando se rechaza developer, a menudo estás en una capa compatible más antigua, un proveedor que replica un schema anterior, o una combinación Azure/API-version que no lo adoptó. Verifica base URL, endpoint y version antes de cambiar roles. Dos llamadas pueden verse iguales en el cliente y aplicar contratos distintos en el servidor.
Cuando se rechaza tool, inspecciona todo el protocolo de tools. Un tool result moderno suele necesitar tool call id y una forma de mensaje compatible con la API elegida. El antiguo function es otra rama. Mezclar function legacy con tool-call moderno puede producir role error aunque el modelo soporte tools.
Si el rol es null, falta, tiene mayúsculas incorrectas o se serializa como enum no reconocido, es un bug de construcción del cliente. Valida antes de enviar:
const allowedRoles = new Set(["developer", "user", "assistant", "tool"]);
for (const [index, message] of messages.entries()) {
if (!allowedRoles.has(message.role)) {
throw new Error("Unsupported outbound role at messages[" + index + "].role");
}
}
La validación local no reemplaza la documentación del proveedor, pero evita que requests malformadas lleguen a producción.
El framework puede inyectar el rol que falla
Muchos equipos dicen “yo nunca envié system”. Puede ser cierto en la capa de aplicación y falso en el HTTP final. Agent frameworks, librerías de chat UI, wrappers RAG, tracing middleware y provider adapters pueden construir el array final después de tu código.
| Capa | Qué inspeccionar | Patrón de fallo |
|---|---|---|
| Agent framework | Default system prompt, memory prompt, tool policy, guardrail message | Aparece un system o developer oculto antes del input |
| SDK adapter | Conversión OpenAI-compatible, enum mapping, soporte legacy de tools | Los nombres de rol se remapean o serializan distinto |
| Gateway | Provider mode, upstream route, model alias | El gateway acepta, pero upstream rechaza el contrato |
| Azure route | Deployment name, api-version, region, model family | El mismo payload funciona en un deployment y falla en otro |
| Middleware | Retry wrapper, prompt decorator, request logger | Un wrapper agrega instruction message en cada llamada |
El punto correcto de observación es el límite más cercano al HTTP request. Si solo ves el objeto previo al framework, todavía no estás viendo el artifact que validó el servidor. El JSON final desinfectado te da índice, rol, endpoint y adapter path.
Verifica con una prueba mínima en la misma ruta
Que funcione en otra ruta no prueba que la ruta fallida esté arreglada. Misma ruta significa mismo provider, base URL, endpoint, API version, model o deployment alias, SDK adapter, framework settings y tool protocol.
Usa el payload más pequeño que todavía ejercite la rama reparada:
curl "$BASE_URL/v1/chat/completions" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "your-selected-model",
"messages": [
{"role": "developer", "content": "Answer in one sentence."},
{"role": "user", "content": "Say ready."}
]
}'
Cambia una sola variable por vez. Si pasas de Azure a OpenAI directo, cambias API version, renombras el modelo, quitas el framework y acortas el prompt al mismo tiempo, el resultado ya no diagnostica la ruta original.
Después de que la prueba mínima pase, ejecuta el flujo completo con role logging activo. Así detectas frameworks que reintroducen system, developer, tool o function después de que el ejemplo mínimo ya funcionaba.
Escala con evidencia, no con una captura
Si el repro mínimo en la misma ruta sigue fallando, prepara un paquete para el proveedor, el equipo de Azure, el operador del gateway o el maintainer del framework. Una captura de pantalla no basta; suele faltar route, version, adapter y request id.
Incluye:
- request JSON desinfectado, sin secretos ni datos de usuario
- base URL y path exactos, o nombre de provider route
- model, deployment, region y API version
- versiones de SDK, framework, adapter y gateway
- response body, status code,
paramycode - request ID, correlation ID o gateway trace ID
- minimal same-route repro y una reparación controlada que intentaste

No incluyas API keys, bearer tokens, prompts privados, texto de clientes, contenido de archivos, salidas de tools ni capturas con secretos. Si el proveedor confirma que la ruta no acepta el rol necesario, las opciones honestas son cambiar a un endpoint con instruction surface adecuada, configurar el adapter para preservar prioridad, o documentar que el fallback cambia comportamiento.
Checklist de recuperación rápida
- Captura response body y el
paramfallido. - Loguea el outbound JSON final después de framework y adapters.
- Identifica el dueño: direct OpenAI, Azure, provider gateway, SDK o framework.
- Mueve la instrucción al canal de mayor prioridad que soporte esa ruta.
- Valida
tooly legacyfunctionpor separado. - Ejecuta un minimal same-route smoke test.
- Ejecuta el flujo completo con role logging activo.
- Escala con sanitized packet si todavía falla.
Ese orden mantiene clara la señal y protege la jerarquía de instrucciones. Evita el arreglo rápido de “ponerlo en user”, que puede quitar el 400 mientras debilita el control del modelo.
Preguntas frecuentes
¿Significa que mi API key está mal?
Normalmente no. Una key incorrecta falla en autenticación antes de validar roles. Este error significa que la ruta pudo leer el payload y rechazó un rol concreto.
¿Debo reemplazar system por developer en todo?
No. developer solo aplica a rutas de Chat Completions que lo soportan. Responses usa instructions, y Azure o gateways compatibles pueden tener otro contrato.
¿Es seguro mover system instructions a user?
Solo como último fallback y tratándolo como cambio de comportamiento. Un user message no suele tener la prioridad de system, developer o instructions.
¿Por qué aparece después de actualizar el framework?
El framework pudo cambiar el role mapping, insertar un instruction message oculto o pasar de function legacy a tool format moderno. Revisa el HTTP payload final.
OpenAI directo funciona, pero mi proveedor compatible falla
Entonces la diferencia está en el contrato del provider route. Guarda payload, endpoint, model alias y trace, y pregunta qué roles y tool-message shapes acepta esa ruta.
¿Qué guardo para soporte?
Request JSON desinfectado, endpoint exacto, model o deployment, API version, versiones de SDK y framework, response body, param, request ID y minimal same-route repro. Elimina secretos y datos de usuario.
