Anatomía completa del request
Objetivo
Al terminar sabrás qué hace cada campo del body de POST /v1/messages, cuáles son obligatorios vs opcionales, y cuándo conviene tocarlos. Vas a poder leer cualquier request de la API y entender en un vistazo qué comportamiento está pidiendo.
Concepto
El body de /v1/messages es un JSON con unos pocos campos obligatorios y un puñado de opcionales muy bien pensados. A diferencia de APIs viejas con docenas de knobs, Anthropic es deliberadamente parca: hay ~10 campos que vas a tocar en la vida real, y cada uno tiene un propósito claro.
Los 3 campos obligatorios
Con esos tres campos ya tenés un request válido. Los demás son opcionales, pero los vas a usar muchísimo.
Los campos opcionales que importan
system — contexto persistente
El system es la instrucción de alto nivel que pone a Claude en un rol o le da reglas que deben aplicar a todos los turnos. No va dentro de messages — es un campo top-level del body.
Puede ser una string simple:
{
"system": "Eres un asistente técnico que responde siempre en español y con ejemplos ejecutables.",
"messages": [...]
}2
3
4
O un array de bloques de texto (formato que vas a usar cuando habilites prompt caching en el Módulo 6):
{
"system": [
{
"type": "text",
"text": "Eres un asistente técnico...",
"cache_control": { "type": "ephemeral", "ttl": "5m" }
}
]
}2
3
4
5
6
7
8
9
La Lección 04 está dedicada íntegramente a system, así que por ahora solo notá que existe y es top-level.
temperature — aleatoriedad del sampling
Controla cuánto "se anima" Claude a elegir tokens menos probables. Rango 0.0 a 1.0 (en Anthropic no es 0-2 como en otras APIs).
0→ máximamente determinístico. Úsalo para extracción estructurada, traducción fiel, código que necesitás que sea idéntico turno a turno. Importante: no es fully deterministic — la propia doc advierte que incluso contemperature: 0puede haber pequeñísima variación por paralelismo interno. Es determinístico "para efectos prácticos", no criptográficamente.0.3–0.7→ rango de "creatividad controlada" para tareas conversacionales normales.1.0→ máxima diversidad. Lo vas a querer para brainstorming, generación de variantes, etc.
Default si no lo pasás: el modelo tiene un default sano que nunca es el extremo — suficiente para conversación. Si te importa la consistencia del output, bajalo explícitamente; si no te importa, no lo toques.
top_p y top_k — sampling avanzado
Son formas alternativas (o complementarias) de acotar el sampling:
top_p(0–1) — nucleus sampling. Solo considera el conjunto más pequeño de tokens cuya probabilidad acumulada superatop_p.top_p=0.9es conservador;top_p=1.0es "sin recorte".top_k(integer) — solo considera losktokens más probables.top_k=40limita brutalmente la cola larga.
Recomendación de la propia doc de Anthropic: "Usually you only need to use temperature. You should alter temperature or top_p, but not both". Traducción operativa: no toques top_p ni top_k salvo que estés haciendo research, evals reproducibles, o replicando un setup específico. En el 95% de los proyectos, temperature sola alcanza.
stop_sequences — el freno explícito
Un array de strings. Si Claude genera cualquiera de esas strings, corta la generación justo antes de esa string (no la incluye en el output) y el stop_reason de la respuesta pasa a ser "stop_sequence" con stop_sequence indicando cuál fue.
Casos típicos:
- Vas a postear el output dentro de un template y querés evitar que Claude se pase de largo:
"stop_sequences": ["</answer>"]. - Estás implementando un REPL y querés cortar en
"\nUser:". - Querés que Claude genere una lista y pare en un marcador final.
Ojo: el string cortado no aparece en el output. Si le pedís "termina con END" y pasás stop_sequences: ["END"], en el content[0].text NO va a estar "END". La señal de que pasó es stop_reason === "stop_sequence".
metadata.user_id — tracking per usuario final
Un objeto con una sola llave útil hoy: user_id (una string opaca — no pongas PII como email o nombre). Sirve para:
- Analytics: en el dashboard vas a poder filtrar consumo por user_id.
- Abuse detection: Anthropic usa
user_idcomo señal para detectar abuso de tu propio servicio sin afectar a tus otros usuarios. - Rate limiting granular: en tiers avanzados podés pedir rate limits por user_id.
Recomendación fuerte del curso: usá metadata.user_id desde el día 1 aunque tu app tenga 3 usuarios. Más adelante (Módulo 11, Admin API) te vas a agradecer tener el tracking per-usuario sin tener que instrumentar nada extra.
Usá un identificador opaco y estable: un UUID, un hash del email, un user_<id> — cualquier cosa que no sea la identidad real. La Messages API no es el lugar para filtrar datos personales.
stream — SSE en vez de respuesta única
Booleano. Si true, la respuesta no es un JSON único sino un stream de Server-Sent Events. Lección 06 entera dedicada a esto.
service_tier — cola de atención
Opcional. Controla cómo Anthropic enruta tu request cuando la infra está saturada:
"auto"(default) — Anthropic decide."standard_only"— no caigas a tiers degradados, fallá si no hay capacidad estándar.
En el 99% de los proyectos no lo toques. Lo vas a ver en el Módulo 6 (Optimización) cuando hablemos de batch vs real-time.
Lo que no está en el request (y es intencional)
- No hay
seed. Si querés reproducibilidad, usátemperature: 0y acordate que aún así no es bit-exacto. - No hay
presence_penaltynifrequency_penalty. Anthropic no expone esos knobs de OpenAI — el comportamiento lo controlás con el system prompt. - No hay
npara pedir múltiples completions. Si querés 5 variantes, hacés 5 requests (y en ese caso mirá batching, Módulo 6).
Esta parsimonia es una decisión de diseño. Menos knobs = menos formas de romper tu prompt en silencio.
Ejecución real
Veamos tres campos opcionales "en acción" con curls reales.
Paso 1 — stop_sequences freno explícito
curl -s 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-haiku-4-5",
"max_tokens": 200,
"stop_sequences": ["END"],
"messages": [
{"role": "user", "content": "Enumera los 5 primeros números primos, uno por línea. Cuando termines escribe la palabra END en una nueva línea."}
]
}'2
3
4
5
6
7
8
9
10
11
12
Respuesta real (resumida):
{
"model": "claude-haiku-4-5-20251001",
"content": [
{ "type": "text", "text": "2\n3\n5\n7\n11\n" }
],
"stop_reason": "stop_sequence",
"stop_sequence": "END",
"usage": { "input_tokens": 40, "output_tokens": 12, "...": "..." }
}2
3
4
5
6
7
8
9
Observá:
content[0].texttermina en"11\n"— la "END" no aparece.stop_reasonpasó a"stop_sequence"(en vez del habitual"end_turn").stop_sequencecontiene cuál fue exactamente (útil si pasaste varias).
Esa combinación stop_reason + stop_sequence es tu mecanismo para saber "la generación se cortó por el freno que yo puse, no porque Claude haya terminado naturalmente".
Paso 2 — temperature: 0 para reproducibilidad
curl -s 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-haiku-4-5",
"max_tokens": 60,
"temperature": 0,
"messages": [{"role": "user", "content": "Inventa un nombre de gato en una sola palabra. Solo la palabra, sin nada más."}]
}'2
3
4
5
6
7
8
9
10
Respuesta real:
{
"content": [ { "type": "text", "text": "Misifu" } ],
"stop_reason": "end_turn",
"usage": { "input_tokens": 30, "output_tokens": 7, "...": "..." }
}2
3
4
5
Correlo varias veces. Lo más probable es que siga devolviendo "Misifu" o alguna de 2-3 opciones fijas. Sin temperature: 0, cada corrida te va a dar nombres distintos — más divertido para humanos, más frustrante para tests automatizados. Para extracción estructurada y tests, temperature: 0 es casi siempre lo que querés.
Paso 3 — metadata.user_id opaco
curl -s 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-haiku-4-5",
"max_tokens": 40,
"metadata": {"user_id": "user_7f9c1b3a-opaque-id"},
"messages": [{"role": "user", "content": "Responde solo OK"}]
}'2
3
4
5
6
7
8
9
10
Respuesta real:
{
"content": [ { "type": "text", "text": "OK" } ],
"stop_reason": "end_turn",
"usage": { "input_tokens": 12, "output_tokens": 4, "...": "..." }
}2
3
4
5
La respuesta es idéntica a si no hubieras pasado metadata — el user_id no afecta el contenido. El efecto lo vas a ver en Analytics del dashboard, donde vas a poder desagregar consumo por ese identificador. No vas a ver feedback inmediato en este curl; es infraestructura para el futuro.
Probalo con tu API key
Tu propia API key queda en el localStorage de tu navegador. Los requests los paga tu workspace y podés ajustar el prompt libremente.
Clasificador con system prompt
Probá el role del system prompt: forzar al modelo a responder solo con una palabra. Editá el system para romper el contrato y ver qué pasa.
💡 Bajá max_tokens a 10 y mirá cómo se corta — podés forzar respuestas más compactas.
Anti-patterns
- ❌ Tocar
top_pytemperaturejuntos. La propia doc dice explícitamente: alterá una o la otra, no las dos. Si no sabés por qué estás tocandotop_p, no lo toques. - ❌ Pasar emails, nombres o teléfonos en
metadata.user_id. Es el lugar perfecto para filtrar PII por accidente. Usá un hash o un UUID. - ❌ Olvidarte de
max_tokens. No es opcional — la API te devuelve 400. Ponele un número generoso pero finito (por ejemplo,1024para respuestas conversacionales,4096para outputs largos). Si chocás mucho constop_reason: "max_tokens", subí el número. - ❌ Asumir que
temperature: 0es bit-exacto. No lo es. Es reproducible para efectos prácticos (mismo output ~siempre en prompts cortos), pero no es seed-determinístico. - ❌ Meter el
systemdentro demessagescomo primer turno"role": "system". Esa sintaxis no existe en Anthropic. En la Messages API los roles válidos son"user"y"assistant"—"system"es un campo top-level del body, no un rol. Es un error que importan mucho los que vienen de OpenAI. - ❌ Meter la misma instrucción en
systemy dentro del primeruser. Redundancia que infla input tokens por las dudas. Elegí uno: si es regla global, va ensystem; si es contexto específico del turno, va enuser. - ❌ Escribir
stop_sequencescon mayúsculas/minúsculas no intencionales. La coincidencia es exacta.["END"]no va a cortar en"end". Revisá qué forma le estás pidiendo a Claude que genere.
Recap
- 3 campos obligatorios:
model,max_tokens,messages. Con eso sólo ya tenés un request válido. - Campos opcionales que importan:
system(top-level, no rol dentro de messages),temperature(0–1),stop_sequences,metadata.user_id(opaco, usalo desde el día 1),stream. - Sampling avanzado (
top_p,top_k) está disponible pero no lo toques salvo investigación. Anthropic recomienda quedarte contemperature. - Lo que no está es intencional: no hay
seed, no haypresence_penalty, no hayn. Menos knobs, menos formas de romper tu prompt en silencio.
Fuente oficial: platform.claude.com/docs/en/api/messagesEjercicio: (esta lección no tiene ejercicio asociado directo — los parámetros se ejercitan en ex-01-02-system-prompt y ex-01-06-error-matrix)