
Structured Outputs de Claude en producción: JSON garantizado por esquema para extraer CFDI y enrutar webhooks
Desde el 4 de febrero de 2026, Structured Outputs de Claude está disponible en general en la Developer Platform y Bedrock. Mandas output_config.format con type "json_schema"; Claude compila tu esquema en una gramática y restringe la generación de tokens, así la salida no puede violar el esquema. Garantiza la FORMA, no la VERDAD: aún validas valores como el RFC y los totales.
¿Qué se lanzó realmente el 4 de febrero de 2026?
El 4 de febrero de 2026, Structured Outputs de Claude pasó a estar disponible en general en la Claude Developer Platform y en Amazon Bedrock. Esa es la noticia, pero la parte que importa para quienes construimos cerca del dinero es el mecanismo, no el anuncio.
Aquí va el detalle técnico que cambia todo: Claude compila tu JSON Schema en una gramática y restringe la generación de tokens durante la inferencia. En corto, el modelo literalmente no puede emitir un token que viole tu esquema. No es “le pides JSON en el prompt y cruzas los dedos” — es una garantía a nivel de generación. El esquema compilado se cachea alrededor de 24 horas, así que peticiones repetidas con el mismo esquema no vuelven a pagar el costo de compilación.
Está soportado en los modelos recientes — claude-opus-4-8, claude-sonnet-4-6 y la familia 4.5 — pero la lista cambia, así que revisa los docs oficiales para ver el listado actual. El header sigue siendo anthropic-version: 2023-06-01.
Opinión, y la marco como opinión: para quien construye pagos o facturación, esta es la feature más aburrida-pero-fundamental del trimestre. Borra una clase entera de código de reintentos y parseo de tu producción. No es glamoroso. Es justo el tipo de cosa que te deja de despertar a las 3 de la mañana.
Si quieres el contexto completo, está el anuncio oficial y los docs de la plataforma.
La API exacta: output_config.format (no output_format)
Aquí es donde la gente se equivoca, y los nombres de los campos son precisamente lo que importa. Mandas un objeto output_config que contiene un format de tipo json_schema. Esta es la forma canónica:
{
"output_config": {
"format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"rfc_emisor": { "type": "string" },
"total": { "type": "number" }
},
"required": ["rfc_emisor", "total"],
"additionalProperties": false
}
}
}
}
El combo de additionalProperties: false más un array required explícito es lo que aprieta el esquema. Sin ellos, el modelo tiene espacio para agregar campos que no esperas o para omitir los que necesitas.
Dos desambiguaciones que evitan errores reales:
Desambiguación 1. El parámetro canónico de la API cruda es output_config.format. El SDK de Python tiene un método messages.parse() que acepta un argumento de conveniencia output_format y lo traduce internamente a output_config.format — pero si trabajas contra la API cruda (curl, fetch, tu propio cliente), usa output_config.format.
Desambiguación 2. La bandera strict: true NO es para salidas JSON. Vive en la definición de una herramienta (tools[].strict) y garantiza el esquema de ENTRADA de la tool. Es otra feature; no las confundas.
Y el límite, dicho de frente antes de que te emociones: esto garantiza la FORMA del JSON, no la corrección de los VALORES dentro de él.
Antes y después: matar el paso de JSON.parse y rezar
Déjame describir el dolor concreto que esto elimina, porque lo viví.
Antes: le pides al modelo “regresa JSON”, luego haces JSON.parse() dentro de un try/catch, le quitas con regex las comas finales o las cercas de markdown ```json, reintentas cuando falla, y aun así te marcan una alerta cuando un campo regresa null o como string en lugar de número. Cada uno de esos pasos es código que mantienes y que se rompe en producción justo con el reporte de bug más feo de tu cola.
Después: la respuesta es un objeto garantizado por esquema. El parseo no puede tronar por la forma. Los bugs de “campo requerido faltante” y “tipo equivocado” desaparecen antes de que tu código corra.
Lo que NO cambia: un objeto con forma garantizada todavía puede traer un RFC equivocado, un IVA que no cuadra, o un enum que el modelo eligió mal. La forma está resuelta; la verdad sigue siendo tu chamba.
Ese es exactamente el modelo mental del resto del post: confía en la forma que te da Claude, valida los valores tú mismo.
Antes (prompt-and-parse)
- JSON.parse() dentro de try/catch
- Regex para reparar comas y cercas markdown
- Loop de reintentos cuando falla
- Alertas por null o tipo equivocado
- Código frágil que mantienes tú
Después (output_config.format)
- Objeto garantizado por esquema
- El parseo no puede tronar por la forma
- Cero bugs de campo faltante o tipo malo
- Sin reintentos por forma rota
- Pendiente: validar los VALORES tú
Tarea A — extraer campos de CFDI de un ticket hecho un desastre
Primer caso real de pagos, y es el que más me importa. El trabajo: sacar rfc_emisor, rfc_receptor, subtotal, iva, total y un array conceptos[] (con descripción, cantidad, valor unitario, importe) de un ticket o factura hechos un desastre, para luego armar el CFDI.
Aquí va la petición, etiquetada como ilustrativa — verifica los nombres de campo contra los docs antes de pegarla:
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-4-8",
"max_tokens": 2048,
"messages": [
{ "role": "user", "content": "Extrae los campos fiscales de este ticket: ..." }
],
"output_config": {
"format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"rfc_emisor": { "type": "string" },
"rfc_receptor": { "type": "string" },
"subtotal": { "type": "number" },
"iva": { "type": "number" },
"total": { "type": "number" },
"conceptos": {
"type": "array",
"items": {
"type": "object",
"properties": {
"descripcion": { "type": "string" },
"cantidad": { "type": "number" },
"valor_unitario": { "type": "number" },
"importe": { "type": "number" }
},
"required": ["descripcion","cantidad","valor_unitario","importe"],
"additionalProperties": false
}
}
},
"required": ["rfc_emisor","rfc_receptor","subtotal","iva","total","conceptos"],
"additionalProperties": false
}
}
}
}'
Como la generación está restringida por la gramática, nunca te va a regresar iva como el string "160.00 pesos" ni un total ausente. El tipo y la presencia están garantizados. Eso solo ya te ahorra el 80% del código sucio de extracción.
Pero — y repito el caveat en contexto — un conceptos con forma garantizada todavía puede tener un importe que no sea igual a cantidad * valor_unitario. Esa reconciliación es validación de valores (la siguiente sección), no algo que el esquema pueda forzar.
Ingeniero, no contador: el esquema no conoce las reglas del SAT. clave_prod_serv, el uso de CFDI y el régimen fiscal los gobierna el SAT, no tu JSON Schema. Con los campos extraídos puedes armar el CFDI con Stripe o Mercado Pago, pero la validez fiscal es otra capa.
Tarea B — enrutar un webhook de Stripe / Mercado Pago a una acción tipada
Segundo caso. El trabajo: convertir un payload ruidoso de webhook (o un mensaje de soporte sobre un pago) en una decisión de enrutamiento tipada, en lugar de un switch frágil sobre strings crudos de tipo-de-evento que además difieren entre dos proveedores.
El esquema usa un enum para action y un enum para provider. Como el campo es un enum del esquema, el modelo no puede regresar un valor fuera de tu conjunto permitido — nada de strings de acción sorpresa pegándole a tu router:
{
"output_config": {
"format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"provider": { "type": "string", "enum": ["stripe", "mercado_pago"] },
"action": {
"type": "string",
"enum": ["capture_payment", "issue_refund", "retry_charge", "flag_review", "ignore"]
},
"amount": { "type": "number" },
"currency": { "type": "string", "enum": ["MXN", "USD"] }
},
"required": ["provider", "action", "amount", "currency"],
"additionalProperties": false
}
}
}
}
Importante, y esto no es negociable: verifica el webhook real con la firma del proveedor (Stripe-Signature o el x-signature de Mercado Pago) ANTES de pasárselo a cualquier modelo. Structured Outputs es para clasificar, no para autenticar. Tengo una guía completa sobre verificar la firma del webhook de Stripe — hazlo primero, siempre.
Caveat: un enum garantiza que el valor está EN tu conjunto, no que sea el CORRECTO para este evento. Incluye siempre un default seguro como flag_review y nunca muevas dinero automáticamente solo porque el modelo lo dijo.
La forma está garantizada. La verdad no. Aquí va la capa de validación de valores.
Esta es la sección que separa un post serio de un anuncio reciclado, así que lo digo sin rodeos: Structured Outputs garantiza que la respuesta es JSON válido que cumple tu esquema. Los VALORES todavía pueden estar equivocados o alucinados. Es la misma lógica que aplico cuando hablo de chatbots de IA sin alucinaciones — forma no es verdad.
Validadores concretos, no hand-waving:
RFC. Valida el formato y el dígito verificador. Un RFC con la longitud y el patrón correctos puede ser perfectamente un identificador fiscal fabricado. Corre tu propia validación de formato y checksum del RFC.
Totales. Reconcilia. Antes de confiar en la extracción, afirma que subtotal + iva sea igual a total y que la suma de los importe de conceptos sea igual a subtotal, con tolerancia de un centavo:
const EPS = 0.01;
const sumaConceptos = conceptos.reduce((acc, c) => acc + c.importe, 0);
const totalCuadra = Math.abs(subtotal + iva - total) < EPS;
const conceptosCuadran = Math.abs(sumaConceptos - subtotal) < EPS;
if (!totalCuadra || !conceptosCuadran) {
// no confíes en la extracción: manda a revisión humana
enviarARevision(documento);
}
Enums y enrutamiento. Trata la acción elegida por el modelo como una sugerencia. Mantén un default seguro y una ruta de revisión humana para casos de alto valor o baja confianza.
Opinión, marcada como tal: la arquitectura correcta es Claude para la FORMA más tu código determinista para la VERDAD. Saltarte la segunda mitad es exactamente cómo terminas emitiendo un CFDI con un total que no suma.
Ingeniero, no contador: para CFDI las reglas del SAT gobiernan. Este post es el arnés de ingeniería, no asesoría fiscal — confirma la corrección fiscal con tu contador.
Ponlo en producción: la receta de cinco pasos
- 1. Define el JSON SchemaadditionalProperties: false y required explícito por cada campo del que dependes
- 2. Manda output_config.formattype json_schema, header anthropic-version 2023-06-01; reusa el esquema para el caché de ~24h
- 3. Confía en la FORMAtira el regex de reparación y el loop de parse-reintento
- 4. Valida los VALORESchecksum del RFC, totales que reconcilian, default seguro en enums — no negociable cerca del dinero
- 5. Actúaarma el CFDI, enruta el webhook, con revisión humana antes de algo irreversible
Esos cinco pasos son toda la receta. El punto fino del paso 2: reusa el mismo esquema entre peticiones para que el caché de ~24 horas trabaje a tu favor. El punto fino del paso 4: para dinero o fiscal, la validación de valores no es opcional.
Si quieres más patrones operativos para poner agentes en producción, tengo más guías de agentes de IA.
Preguntas frecuentes
¿El parámetro es output_config u output_format?
El canónico de la API cruda es output_config.format con type: "json_schema". output_format sobrevive como conveniencia del SDK de Python que mapea a él; prefiere output_config.format.
¿strict: true aplica a salidas JSON?
No. strict: true va en la definición de una herramienta (tools[].strict) para validar la ENTRADA de la tool. Es otra feature.
¿Detiene las alucinaciones? No. Garantiza la forma del JSON, no la verdad de los valores. Valida tú los RFC, los totales y los enums.
¿Qué modelos lo soportan? Modelos recientes de Claude, incluidos claude-opus-4-8, claude-sonnet-4-6 y la familia 4.5; revisa los docs para la lista actual.
¿Dónde está disponible? Disponible en general en la Claude Developer Platform y Amazon Bedrock desde el 4 de febrero de 2026 (confirma la disponibilidad actual por plataforma).
¿Lo puedo usar directo para CFDI? Extrae los campos de forma confiable (la forma), pero las reglas del SAT siguen gobernando la validez fiscal — ingeniero, no contador.
La conclusión en una línea
Deja que Claude garantice la forma con output_config.format; tú garantizas la verdad con el checksum del RFC y los totales que reconcilian. Esa división es lo que hace que Structured Outputs sea seguro de poner cerca del dinero.