Tu primera palmadita POST: cómo enviar datos y autenticarte en una API


1. Tu primer POST

En el post anterior hicimos GET — le pedimos algo al servidor. El POST es lo contrario: le enviamos algo.

Antes de ver el comando, los flags de curl que vamos a usar:

Parte Flag Ejemplo
Método -X -X POST
URL (sin flag) "https://..."
Header -H -H "Content-Type: application/json"
Body -d -d '{"clave": "valor"}'

-H viene de Header — puedes poner varios en un mismo comando, uno por cada header. -d viene de Data — es el body de la petición.

curl -X POST "https://jsonplaceholder.typicode.com/posts" \
  -H "Content-Type: application/json" \
  -d '{"titulo": "mi primer post", "cuerpo": "hola mundo", "usuarioId": 1}'

Respuesta del servidor:

{
  "titulo": "mi primer post",
  "cuerpo": "hola mundo",
  "usuarioId": 1,
  "id": 101
}

El servidor recibió los datos, les asignó un id y los devolvió como confirmación.

Diferencias con GET

GET POST
Headers Se envían automáticamente; en un GET básico no necesitas añadir ninguno tú (aunque puedes) Se envían automáticamente, pero debes añadir Content-Type tú mismo cuando envías un body
Tienes body de petición No
Tienes body de respuesta Sí (los datos que pediste) Sí (confirmación de lo que creaste)
Los datos van en la URL Sí (?texto=hola) No — van dentro del mensaje

El status code de un POST exitoso

Agrega -i al comando para ver los headers completos:

curl -i -X POST "https://jsonplaceholder.typicode.com/posts" \
  -H "Content-Type: application/json" \
  -d '{"titulo": "mi primer post", "cuerpo": "hola mundo", "usuarioId": 1}'
HTTP/2 201
location: https://jsonplaceholder.typicode.com/posts/101
cache-control: no-cache
...

Tres cosas para notar:

  1. 201 Created — no es un 200. El servidor te dice específicamente "lo creé". El 200 es "aquí tienes lo que pediste"; el 201 es "lo creé y aquí está".
  2. location — aparece solo en respuestas de creación. El servidor te dice dónde vive el recurso que acabas de crear. En una API REST bien diseñada, un GET a esa URL siempre devuelve lo que creaste — ese es el contrato. Dos excepciones: si el recurso es privado necesitas autenticación (sin ella obtienes 401 o 403), y hay APIs mal diseñadas donde el endpoint GET no está implementado aunque devuelvan location.
  3. cache-control: no-cache — en un POST no hay nada que cachear. El servidor lo deja explícito con tres headers: cache-control: no-cache, expires: -1 y pragma: no-cache.

2. El body de petición

El body de petición es lo que viaja dentro del mensaje cuando haces un POST. No aparece en la URL — va oculto adentro.

El flag -d en curl es el body:

Por qué el header Content-Type es obligatorio

El servidor recibe el body como bytes. Sin el header Content-Type no sabe en qué formato vienen esos bytes — si es JSON, un formulario HTML, texto plano u otra cosa.

Sin Content-Type:

curl -X POST "https://jsonplaceholder.typicode.com/posts" \
  -d '{"titulo": "mi primer post", "cuerpo": "hola mundo", "usuarioId": 1}'

Respuesta: {}

El body llegó igual — pero el servidor no pudo parsearlo y devolvió un objeto vacío. El -H "Content-Type: application/json" es lo que le dice "lo que te envío es JSON, trátalo como tal".

3. API de prueba vs API real

Después de hacer el POST, lo lógico es hacer un GET al recurso que "creamos":

curl "https://jsonplaceholder.typicode.com/posts/101"

Respuesta: {}

No encontró nada. JSONPlaceholder es una API falsa: simula el comportamiento de un POST real — devuelve 201, asigna un id, incluye el header location — pero no guarda nada en ninguna base de datos. Existe para aprender el mecanismo de HTTP sin necesitar infraestructura real.

En una API real el flujo sería:

1. POST /posts   → servidor guarda en base de datos → status 201 + id: 42
2. GET /posts/42 → servidor busca en base de datos  → status 200 + datos del post 42

JSONPlaceholder tiene 100 posts pre-cargados (del 1 al 100). Cualquier GET a esos IDs funciona. Cualquier cosa "creada" con POST no persiste.

4. Peticiones con y sin autenticación (GET/POST/DELETE/PUT)

Hasta aquí trabajamos exclusivamente con POST, pero la autenticación no es exclusiva de ningún método — un GET, un DELETE, un PUT también pueden requerirla. A partir de esta sección ya no hablamos solo de POST: hablamos de cómo cualquier petición HTTP cambia cuando una API requiere que te identifiques.

La diferencia es concreta: un header adicional.

Sin autenticación — lo que hiciste en las secciones anteriores:

curl -X POST "https://jsonplaceholder.typicode.com/posts" \
  -H "Content-Type: application/json" \
  -d '{"titulo": "mi primer post"}'

Con autenticación:

curl -X POST "https://api.ejemplo.com/posts" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer eyJhbGci..." \
  -d '{"titulo": "mi primer post"}'

El body, el método y el Content-Type son idénticos. Lo único que cambia es el header Authorization. Sin él, el servidor responde 401 Unauthorized y no procesa nada. Con él, te identifica y ejecuta la petición igual que antes.

4.1 Por qué las APIs requieren ese header de authorization

Cuando hiciste el POST a JSONPlaceholder, cualquier persona en el mundo puede hacer exactamente lo mismo — no hay nada que los detenga. Eso funciona para una API de prueba, pero en una API real el servidor necesita responder dos preguntas antes de procesar cualquier petición:

  1. ¿Quién eres? — identidad
  2. ¿Qué puedes hacer? — permisos

Sin esa respuesta, no puede distinguir entre tú y cualquier otra persona que conozca la URL.

Una analogía: una API sin autenticación es como un edificio sin recepción — cualquiera entra a cualquier piso. La autenticación es la recepción que te pide el nombre y te da solo las llaves que te corresponden.

4.2 Cómo funciona la API key

El concepto es uno: una credencial estática que generas desde el panel del servicio, la copias y la incluyes en cada petición. No expira automáticamente y no requiere ningún proceso de login programático.

Lo que varía entre APIs es el header que usan para recibirla. Los tres formatos más comunes:

Authorization: Bearer <credencial> — el más extendido. Lo usan GitHub (Personal Access Token), Notion (Integration Token) y OpenAI (API key). Aunque cada servicio tiene su propio nombre para la credencial, todas viajan en el mismo header.

curl -X GET "https://api.ejemplo.com/datos" \
  -H "Authorization: Bearer mi-credencial"

x-api-key: <clave> — formato alternativo que usan servicios como AWS API Gateway. También puede viajar como query param (?api_key=<clave>), lo que lo hace fácil de probar en el navegador pero menos seguro: la clave queda visible en los logs del servidor y en el historial.

curl -X GET "https://api.ejemplo.com/datos" \
  -H "x-api-key: mi-clave"

Header propio del servicio — algunos servicios definen su propio header. Shopify, por ejemplo, usa X-Shopify-Access-Token.

curl -X GET "https://tu-tienda.myshopify.com/admin/api/2024-01/products.json" \
  -H "X-Shopify-Access-Token: mi-token"

La regla práctica: antes de hacer tu primera petición, ve a la sección de autenticación de la documentación del servicio. Ahí te dirá exactamente qué header usar y cómo generar la credencial.

Características comunes

  1. Son estáticos — no expiran automáticamente. Viven hasta que los revoques desde el panel.
  2. No hay login programático — los tienes listos antes de hacer cualquier petición.
  3. Son independientes del método HTTP — funcionan igual en un GET, POST, DELETE o cualquier otro.

Existe un patrón diferente donde la credencial se obtiene programáticamente a través de un flujo de autenticación — es la base de OAuth. Lo cubriremos en un post separado.

Referencia oficial