< BLOG />
Cada vez que pegas un pantallazo de Postman en la documentación, muere un gatito
Por qué una captura de Postman no es documentación y cómo entregar ejemplos con cURL que cualquiera pueda copiar, ejecutar y adaptar.
< INICIO />
El otro día nos llegó la documentación de una API con la que teníamos que integrarnos. Documentación es un decir: eran capturas de Postman. Una para la URL, otra para las cabeceras, otra para el body. Y el body salía cortado.
Ahí fue cuando lo pensé: cada vez que alguien pega un pantallazo de Postman en una documentación, muere un gatito.

Que conste: no tengo nada contra Postman. Es una herramienta estupenda para explorar una API, trastear con peticiones y guardar colecciones. El problema empieza cuando confundimos una captura de su interfaz con documentación técnica.
Porque quien tiene que integrarse con tu API no debería reconstruir una petición jugando a las siete diferencias entre pestañas, campos y desplegables de un PNG. Debería poder copiarla, pegarla y ejecutarla.
Y para eso tenemos a un viejo amigo: cURL.
TL;DR
Una captura de Postman obliga al lector a transcribir la petición a mano. Un comando curl le enseña la URL, el método, las cabeceras y el cuerpo en texto plano, listos para copiar, ejecutar, versionar y traducir al lenguaje que use su equipo.
Esto no va de elegir ser de "Team Postman" o "Team Curl", cada cosa tiene su sitio:
- OpenAPI describe formalmente el contrato.
- Swagger UI te deja navegar ese contrato y probarlo desde el navegador.
- Postman es cómodo para explorar y guardar colecciones.
- cURL te da ejemplos concretos, portables y reproducibles.
- Un SDK te ahorra trabajo desde un lenguaje concreto.
El problema no es usar Postman. El problema es entregar tres capturas de Postman y llamarlo documentación.
Y también existen otras opciones: puedes documentar las peticiones con ficheros .http, HTTPie o la herramienta que utilice tu equipo. Lo importante no es la herramienta, es entregar texto que se pueda copiar y ejecutar, no algo que tengas que reproducir a mano y que fácilmente pueda llevarte a error.
¿Qué necesitas para integrarte con una API?
Cosas bastante concretas: la URL, el método HTTP, las cabeceras, cómo va la autenticación, qué forma tiene el body, qué respuesta esperar y qué errores te pueden caer.
Una especificación OpenAPI es el mejor punto de partida para describir todo eso: operaciones, parámetros, esquemas, respuestas y errores. Pero una especificación no es una guía de integración. Si quiero enseñarte a crear un pedido, la explicación gana muchísimo si viene con una petición completa que puedas copiar, pegar y lanzar desde el terminal, y de forma agnóstica, sin asumir que el usuario va a saber Java, Python o lo que sea.
Ahí es donde cURL encaja como un guante.
Anatomía de un postmantazo
Imagina la documentación de antes: te lías con Postman y vas haciendo capturas de cada pestaña. ¿Qué problemas tiene esto?
No se puede copiar. Te toca transcribir la URL, las cabeceras y el JSON a mano. Pierdes tiempo y, de paso, introduces erratas que no existían en la petición original. Ese Autorization te va a llevar un rato darte cuenta de que está mal escrito (los fallos tontos son los que más escuecen, porque no te los esperas).
Oculta información. Y no es una forma de hablar: las cabeceras que añade el propio Postman (Host, User-Agent, Content-Type...) viven colapsadas detrás de un botón que pone hidden. Si la autenticación la has puesto en la pestaña Authorization, o peor, la heredas de la colección con Inherit auth from parent, el Authorization de verdad tampoco sale en la lista de cabeceras. Y los {{valores}} que sí se ven salen de un entorno que solo existe en la máquina de quien hizo la captura. La petición que se envió no es la que se ve en la imagen.
Es un infierno de mantener. Cambias una URL o añades una cabecera y toca rehacer capturas. En texto, Git te dice exactamente qué línea ha cambiado. En un PNG solo te dice que el PNG es distinto. Y no lo puedes buscar, ni seleccionar, ni leer con un lector de pantalla.
Es fácil que se te escape info sensible. Tokens, cookies, identificadores internos, nombres de entornos, datos reales de clientes. Y esas cosas tienen la mala costumbre de acabar en un documento compartido, en un ticket de Jira o en un repositorio... información sensible al alcance de todos.
No es accesible. Imagínate que tienes como compañero al crack de Juanjo Montiel (Desarrollador senior en Microsoft en Dublín que es invidente), directamente te tira el documento a la cabeza, o también si lo quieres pasar por una IA, enhorabuena vas a fundir tokens como un campeón descifrando imágenes.
La interfaz de usuario puede cambiar y te quedas descolocado buscando dónde se ponían las cabeceras.
Te atas a una herramienta en concreto. Todo el mundo a instalársela, y a usar la misma porque patatas.
Una colección de Postman exportada ya sería bastante mejor que una captura: es JSON, se versiona y la puedes importar en otras herramientas como Bruno, Insomnia o Hoppscotch, así que tampoco te ata a Postman. Pero no es algo que pegues en un terminal y ya está: te toca instalar algo que la lea, o newman por npm si la quieres lanzar desde la línea de comandos.
Y puede llegarte incompleta. Los valores de las variables de entorno no viajan en el export: abres la colección y te encuentras {{baseUrl}} y {{token}} en blanco, a preguntar a los compis de dónde sale cada cosa. Y ojo, que el formato tampoco es eterno: Postman 12 estrena uno nuevo (v3) que newman ya no ejecuta, y te mandan a su propia CLI.
Somos desarrolladores... ¿Qué hay más directo y determinista que un comando que podamos copiar, pegar y ejecutar?
Nuestro amigo cURL
cURL viene de Client URL. Es una herramienta de línea de comandos para transferir datos usando URLs, y lleva con nosotros desde 1998. Sigue siendo útil porque hace una cosa muy concreta: construye y envía una petición HTTP sin depender de una interfaz gráfica ni de un lenguaje de programación.
Así se ve una petición completa:
curl https://api.example.com/orders \
--request POST \
--header "Authorization: Bearer $TOKEN" \
--header "Content-Type: application/json" \
--data '{
"productId": "ABC-123",
"quantity": 2
}'No hay que interpretar nada: la URL identifica el recurso, --request es el método, --header añade una cabecera y --data es el cuerpo.
A lo largo del post verás dos tipos de ejemplo. Los que apuntan a
jsonplaceholder.typicode.como ahttpbin.orglos puedes copiar, pegar y ejecutar tal cual. Los que apuntan aexample.com(api.example.com,identity.example.com) son dominios reservados para documentación: no responden, están ahí para enseñar la forma del comando, no para lanzarlos.
Podríamos usar las abreviaturas -X, -H y -d. En documentación yo prefiero las opciones largas: ocupan un poco más, pero quien llega de nuevas las entiende sin tener que memorizar nada.
Vale, abre un terminal
Hasta aquí la teoría. Vamos a ejecutar cosas de verdad.
Abre un terminal y copia y pega estos ejemplos, funciona del tirón :)
Ojo si estás en Windows, lee antes la sección "¿Funciona igual en Windows, Linux y macOS?" de este post
Primero, comprueba que tienes cURL (viene de serie en macOS, en la mayoría de distribuciones Linux y en Windows 10 y 11):
curl --versionPara los ejemplos usaremos JSONPlaceholder, una API pública de pruebas que acepta GET, POST, PUT, PATCH y DELETE sin registro ni credenciales. Un detalle importante: las escrituras son simuladas. La API te responde como si hubiera guardado el cambio, pero no guarda nada. Para aprender nos sobra.
Si vives en España, antes de ejecutar el ejemplo, mira si hay fútbol: https://hayahora.futbol/,
jsonplaceholdertira de Cloudflare y está capado en España mientras haya partidos :(, más info sobre La Liga y Cloudflare. Si te pilla en horario de partido, los ejemplos dehttpbin.orgque salen más abajo sí funcionan: ese no va por Cloudflare.
Traerte una colección:
Vamos a empezar por lo más sencillo, pedirle a la API la lista de posts que tiene. Son cien, así que prepárate para ver el terminal llenarse de JSON:
curl https://jsonplaceholder.typicode.com/postsFíjate en que no aparece --request por ningún lado: si no le dices el método, cURL hace un GET. Como es lo más habitual, te ahorras escribirlo.
Traerte un recurso:
Ahora solo queremos uno de esos cien. Se lo pides añadiendo su id al final de la URL, y te devuelve ese y nada más:
curl https://jsonplaceholder.typicode.com/posts/1Ver también el código de estado y las cabeceras de respuesta:
Hasta ahora solo estamos viendo el cuerpo de la respuesta. Pero cuando algo falla, el cuerpo es lo de menos: lo que necesitas es el código de estado y las cabeceras. Eso te lo saca --include:
curl https://jsonplaceholder.typicode.com/posts/1 --includeY antes del JSON de siempre te aparece esto (te lo recorto, que vienen unas cuantas más):
HTTP/2 200
content-type: application/json; charset=utf-8
content-length: 292
cache-control: max-age=43200
etag: W/"124-yiKdLzqO5gfBrJFrcdJ8Yq0LGnU"
x-ratelimit-limit: 1000
x-ratelimit-remaining: 999
x-powered-by: Express
{
"userId": 1,
"id": 1,
...
}La primera línea es la que importa: HTTP/2 200, ha ido bien. Ahí es donde te va a salir el 404 cuando la URL no existe, el 401 cuando el token ha caducado o el 500 cuando el problema no es tuyo.
Y mira las dos de x-ratelimit: la API te está diciendo que tienes un tope de 1000 peticiones y que te quedan 999. Eso no viaja en el cuerpo, solo en las cabeceras, así que sin --include ni te enteras de que existe.
Crear un recurso:
Vamos a crear un post nuevo. Es el primer comando en el que no basta con pedir una URL: hay que decirle el método y mandarle el contenido en el cuerpo de la petición.
curl https://jsonplaceholder.typicode.com/posts \
--request POST \
--header "Content-Type: application/json" \
--data '{
"title": "Mi primer post",
"body": "Contenido del post",
"userId": 1
}'Si estás en Windows, ojo, que este es el primer comando del post que no se pega tal cual. En CMD las comillas simples no delimitan nada, así que el JSON llega roto, y en PowerShell las dobles de dentro dan guerra según la versión. Lo más cómodo es abrir una terminal de WSL o Git Bash (lo puedes abrir incluso desde el propio VSCode) y seguir como si nada. Si no puedes, saca el cuerpo a un fichero y pásalo con
--data @nuevopost.json. Lo cuento con detalle en ¿Funciona igual en Windows, Linux y macOS?
¿Qué estamos haciendo aquí?
https://jsonplaceholder.typicode.com/posts— la colección, sin ningún id al final. El id lo pone el servidor, que para eso lo estamos creando.--request POST— el método. Curiosidad: en cuanto le pones--data, cURL se pasa aPOSTél solo. Pero en documentación lo escribes igual, que quien lea el comando no tiene por qué saberse esa regla.--header "Content-Type: application/json"— le dices a la API en qué formato va el cuerpo (en este caso JSON). Sáltatelo y muchas te contestan un400o un415sin más explicación.--data '{ ... }'— el cuerpo. Entre comillas simples, para que el shell no se meta con las dobles del JSON.
Y te responde un 201 Created con el objeto que le has mandado, ya con su id:
{
"title": "Mi primer post",
"body": "Contenido del post",
"userId": 1,
"id": 101
}Ojo, que esto es una API de pruebas: el id es siempre 101 y el post no se guarda en ningún sitio. Pídele ahora
/posts/101y te suelta un 404. Eso sí, la respuesta trae una cabeceraLocation: .../posts/101que puedes ver con--include, y eso es lo que hace una API bien educada: decirte dónde ha quedado lo que acabas de crear. En una de verdad, además, el recurso se guardaría en la base de datos del servidor.
Modificar parcialmente:
Ahora vamos a cambiarle el título a ese primer post, y solo el título:
curl https://jsonplaceholder.typicode.com/posts/1 \
--request PATCH \
--header "Content-Type: application/json" \
--data '{ "title": "Nuevo título" }'Aquí ya no hace falta desmenuzarlo, es el mismo esquema de antes con dos cambios: la URL apunta a un recurso concreto (/posts/1, no a la colección) y el verbo es PATCH. En el cuerpo va solo el campo que quieres tocar; el resto se queda como estaba. Esto es el comportamiento normal que te puedes esperar del verbo PATCH, pero depende de quién la implemente te puedes encontrar sorpresas, antes de darlo por asumido, consulta la documentación de la API que tengas que explotar.
¿Y PUT? Prácticamente igual, cambiando el verbo. La diferencia no está en el comando sino en lo que mandas: con PATCH envías solo los campos que cambian y con PUT el recurso completo (todos los campos), tal y como quieres que quede.
curl https://jsonplaceholder.typicode.com/posts/1 \
--request PUT \
--header "Content-Type: application/json" \
--data '{
"id": 1,
"title": "Nuevo título",
"body": "Contenido del post",
"userId": 1
}'Aquí pasa como con PATCH, lo normal es lo que hemos comentado, pero hay APIs que tratan el PUT como si fuera un PATCH, así que siempre es bueno curarse en salud y consultar la documentación de la API concreta con la que vayas a trabajar.
Borrar:
Y para cerrar el ciclo, vamos a ver cómo borrar un post:
curl https://jsonplaceholder.typicode.com/posts/1 --request DELETENi cuerpo ni Content-Type: la URL ya dice lo que te cargas, y por eso cabe en una línea. Eso sí, no esperes gran cosa de vuelta. Aquí te contesta un {}, y en una API real lo normal es un 204 No Content, que es literalmente cuerpo vacío. Si quieres saber si ha ido bien, añade --include al comando, y miras el código de estado.
En cinco minutos y un puñado de líneas has probado el ciclo completo. Y si mañana cambia una cabecera, editas texto y ya está, nada de volver a hacer capturas de pantalla.
Vamos a ver ahora un par de trucos que hacen la vida más fácil.
Sacar el JSON en formato "bonito". Si tienes jq instalado, encadena | jq al final y lo verás formateado y con colores:
curl --silent https://jsonplaceholder.typicode.com/posts/1 | jqEl --silent evita que cURL muestre la barra de progreso mientras pasa la respuesta a jq. Y si solo te interesa un campo, jq '.title' y te quedas con ese.
Importar el body de una petición de un fichero. Cuando el JSON se te va de las manos, sácalo a un fichero aparte. Créalo en la misma carpeta desde la que vayas a lanzar el comando y dale un nombre descriptivo, en nuestro caso: nuevopost.json:
{
"title": "Mi primer post",
"body": "Contenido del post",
"userId": 1
}Y se lo pasas a --data con una arroba delante:
curl https://jsonplaceholder.typicode.com/posts \
--request POST \
--header "Content-Type: application/json" \
--data @nuevopost.jsonLa arroba @ indica a cURL que nuevopost.json es un fichero y que debe utilizar su contenido como cuerpo de la petición. Sin ella, enviaría literalmente el texto "nuevopost.json".
La ruta se busca desde la carpeta en la que estás ejecutando el comando. Si el fichero está en otro sitio, tendrás que indicar su ruta relativa o absoluta.
Un detalle: --data se come los saltos de línea del fichero y te lo manda todo seguido. Para un JSON da igual, pero si necesitas que llegue byte a byte, usa --data-binary @nuevopost.json.
Cabeceras, tokens y cookies
Hasta ahora hemos trabajado con la URL, el método y el cuerpo. En una API real también necesitamos enviar información adicional: el formato del contenido, el tipo de respuesta que esperamos, nuestras credenciales o un identificador que permita seguir la petición entre varios servicios. Todo eso se envía mediante cabeceras.
Tu navegador ya incluye muchas cabeceras automáticamente en cada petición. Con cURL podemos añadir las que necesitemos de forma explícita, así que quien lea el comando puede ver exactamente qué estamos enviando.
Para verlo en vivo cambiamos de banco de pruebas. httpbin es otro servicio público sin registro ni credenciales, y hace una cosa muy concreta: te devuelve en JSON la petición que le acabas de mandar. Cabeceras, método, parámetros y cuerpo, tal y como le han llegado. Es justo lo que hace falta aquí, porque dejas de suponer lo que estás enviando y lo ves. Y además este servicio no va por Cloudflare, así que funciona aunque haya partido de fútbol :).
Las cabeceras van con --header, tantas como necesites:
curl https://httpbin.org/anything \
--header "X-Client-Id: lemoncode" \
--header "X-Correlation-Id: 123456"Y esta es la respuesta:
{
"headers": {
"Accept": "*/*",
"Host": "httpbin.org",
"User-Agent": "curl/8.7.1",
"X-Client-Id": "lemoncode",
"X-Correlation-Id": "123456"
},
"method": "GET",
"url": "https://httpbin.org/anything"
}Aquí puedes pensar, anda, si hay más cabeceras de las que he enviado. Tú has puesto dos y han llegado cinco. Las otras tres las ha añadido cURL por su cuenta:
Host— a qué dominio vas. Es obligatoria en HTTP/1.1, porque en una misma IP pueden convivir muchos sitios y el servidor necesita saber por cuál preguntas.User-Agent— quién llama. cURL se identifica con su nombre y su versión, igual que tu navegador se identifica con el suyo.Accept— qué formato aceptas de vuelta. Ese*/*es un "mándame lo que quieras, que ya me apaño".
Si alguna no te conviene, la pisas poniéndola tú con --header y se acabó.
Postman también añade cabeceras automáticamente, aunque algunas quedan ocultas en su interfaz. Con cURL puedes ver exactamente qué se está enviando utilizando --verbose.
¿Y si lo que necesito mandar es un token de sesión? Pues más de lo mismo, y esa es la buena noticia: un token no tiene nada de especial, es otra cabecera. Se llama Authorization. Existe además una opción específica, --oauth2-bearer, pero en documentación prefiero escribir la cabecera entera: deja a la vista exactamente lo que se está enviando.
curl https://httpbin.org/bearer \
--header "Authorization: Bearer eyJhbGciOi..."Para practicar lo que aprendimos más arriba en este post, prueba aquí a usar
--silenty enlazarjqy a ver cómo sale ;)
httpbin tiene un endpoint, /bearer, que se dedica precisamente a mirar esa cabecera y contarte si ha visto el token y cuál es:
{
"authenticated": true,
"token": "eyJhbGciOi..."
}Si quieres probarlo contra tu API real sería exactamente lo mismo, sólo que cambiando la URL (aquí ponemos una inventada):
curl https://api.example.com/orders \
--header "Authorization: Bearer $TOKEN"Ojo, que este no lo pegues en el terminal esperando respuesta:
api.example.comes uno de esos dominios de ejemplo que te comentaba al principio, no existe y no contesta. Está aquí para que veas la forma del comando. Y donde pone$TOKENiría el token que toque.
Y esto es importante: cuando documentes, no pongas nunca un token de verdad. Pon una variable como $TOKEN, o un valor que cante que es falso (indícale al lector que lo reemplace por el suyo). Un token real en un README acaba en Git, y aunque luego lo borres se queda en el historial. Es una fuga de seguridad esperando a que alguien te reviente.
¿Y las cookies? También son una cabecera, la que se llama Cookie. Podrías mandarlas con --header y funcionaría, pero cURL trae opciones dedicadas que se leen mejor y, como verás enseguida, te permiten hacer más cosas.
Vamos con lo básico: mandarle a httpbin una cookie de sesión y ver qué le ha llegado. La opción es --cookie:
curl https://httpbin.org/cookies --cookie "sessionId=abc123"Y httpbin te devuelve las cookies que le han llegado:
{
"cookies": {
"sessionId": "abc123"
}
}También puede comportarse como un navegador: puedes guardar las cookies que te devuelve el servidor y reenviarlas después.
# Guarda en cookies.txt lo que el servidor mande en Set-Cookie
# (--location porque este endpoint de httpbin responde con un redirect)
curl "https://httpbin.org/cookies/set?sessionId=abc123" --cookie-jar cookies.txt --location
# Y en la siguiente petición lo reenvía
curl https://httpbin.org/cookies --cookie cookies.txtAbre cookies.txt y verás la cookie guardada en formato Netscape, el mismo que usan los navegadores.
Vamos a ponerlo en escena. Imagínate una aplicación en la que el login recibe usuario y contraseña, los valida y, si todo está en orden, te devuelve una cookie con el token de sesión. A partir de ahí, cada petición tiene que llevar esa cookie o te echa. Con cURL son los mismos dos pasos de antes: entras y guardas lo que venga, y luego lo reutilizas.
# 1. Login: manda las credenciales y guarda la cookie que devuelva
curl https://api.example.com/login \
--request POST \
--header "Content-Type: application/json" \
--data '{"email":"user@example.com","password":"secret"}' \
--cookie-jar cookies.txt
# 2. Y ya puedes pedir lo que necesites con esa sesión
curl https://api.example.com/profile --cookie cookies.txtLas dos opciones se parecen mucho, pero hacen cosas contrarias:
--cookie-jar cookies.txtes el tarro: le dice a cURL dónde dejar guardadas las cookies que le mande el servidor (en este caso las escribe a un fichero que hemos llamadocookies.txt).--cookie cookies.txtes el sentido contrario: de dónde sacarlas para enviarlas, en este caso las lee del ficherocookies.txt.
Y aquí va el detalle que despista: --cookie hace dos cosas según lo que le pongas detrás. Si lo que le pasas lleva un =, se lo toma como la cookie en sí (--cookie "sessionId=abc123", el ejemplo de antes). Si no lleva =, entiende que es el nombre de un fichero y lee las cookies de ahí. La misma opción, dos comportamientos, y lo único que los distingue es un igual.
Ojo, que esto es una forma de hacer login, no la forma: otra API puede pedirte un formulario en vez de JSON, o devolverte el token en el cuerpo de la respuesta y pasar de cookies. Lo que no cambia es la mecánica de cURL. (Y ya sabes: api.example.com no existe, no pegues este ejemplo en tu terminal.)
¿Y si la cookie es HttpOnly?
No hay que hacer nada especial. Se manda con --cookie, igual que cualquier otra:
curl https://httpbin.org/cookies --cookie "sessionId=abc123"Sí, es el mismo comando de antes. A cURL le da exactamente igual que la cookie esté marcada como HttpOnly, porque esa marca no va con él.
Conviene entender por qué: HttpOnly es una marca que el servidor le pone a la cookie, y significa una sola cosa: el JavaScript de la página no puede leer su valor con document.cookie.
Sirve para que, si alguien consigue colar un script en tu web, no pueda llevarse tu sesión. Eso sí, protege contra el robo, no contra el uso: ese script todavía puede lanzar peticiones desde la propia página, y el navegador les pondrá la cookie igual, cURL también puede hacerlo si conoces su valor. Por eso no necesitas ningún parámetro especial.
¿Y cómo saco una cookie del navegador?
A veces ya tienes la sesión abierta en el navegador y solo quieres reproducir una petición desde el terminal. Si el acceso depende de Google, Microsoft Entra, un SSO o un doble factor, repetir todo el proceso con cURL puede ser bastante complicado. En esos casos, lo más sencillo es copiar la cookie de sesión que ya tiene el navegador y usarla en el comando.
Hay dos formas de hacerlo:
- A mano. En las DevTools, pestaña Application (en Firefox, Almacenamiento), apartado Cookies. Buscas la de sesión, copias su valor y se lo pasas a cURL con
--cookie "nombre=valor".

- De golpe. En la pestaña Network, buscas una petición que haya ido autenticada, botón derecho y Copy as cURL. Te llevas el comando entero, con todas sus cookies y sus cabeceras, listo para pegar en el terminal. Es lo más cómodo, sobre todo cuando la sesión no es una cookie sino tres.

Al igual que con los tokens: esto está bien para hacer pruebas puntuales, pero nada más. La cookie identifica tu sesión y, mientras siga activa, quien la tenga podría utilizarla para acceder como si fueras tú. No la compartas ni la pegues en un README, un ticket o un mensaje de Slack. Cuando termines la prueba, bórrala del comando y del historial del terminal.
¿Funciona igual en Windows, Linux y macOS?
Sí. Los parámetros de cURL son los mismos en los tres sistemas. Lo que cambia es cómo interpreta cada terminal las comillas, las variables de entorno y los saltos de línea.
En Linux y macOS, si utilizas Bash o Zsh, puedes copiar los comandos de este post tal cual:
curl https://httpbin.org/bearer \
--header "Authorization: Bearer $TOKEN"La barra invertida (\) permite continuar el comando en la línea siguiente y $TOKEN lee el valor de una variable de entorno.
En Windows, la opción más cómoda es utilizar WSL (el Windows Subsystem for Linux) o Git Bash. También puedes abrir cualquiera de los dos desde el terminal integrado de Visual Studio Code. Así tendrás un entorno compatible con los ejemplos anteriores y no necesitarás adaptar los comandos.
Si no te queda otra que trabajar con PowerShell o CMD, solo tienes que tener en cuenta dos diferencias: cómo se continúa un comando en la línea siguiente y cómo se leen las variables de entorno.
En PowerShell, la continuación de línea se indica con un acento grave (un backtick) y las variables de entorno se leen con $env::
curl.exe https://httpbin.org/bearer `
--header "Authorization: Bearer $env:TOKEN"En CMD se utiliza el acento circunflejo (^) para continuar el comando y la variable se escribe entre signos de porcentaje:
curl.exe https://httpbin.org/bearer ^
--header "Authorization: Bearer %TOKEN%"Puedes probar los tres ejemplos después de definir TOKEN con cualquier valor. Si todo está bien escrito, httpbin responderá indicando que ha recibido el token.
Fíjate también en que, en Windows, utilizó curl.exe en lugar de curl. En Windows PowerShell 5.1, curl es un alias de Invoke-WebRequest, que no reconoce las mismas opciones y puede devolver errores bastante desconcertantes. En PowerShell 7 ese alias ya no existe, pero escribir curl.exe evita el problema en ambas versiones.
Donde suele haber más lío es al enviar un body con JSON, porque las comillas no funcionan igual en Bash, PowerShell y CMD. Si vas a publicar documentación para usuarios de los tres sistemas, lo más sencillo es guardar el JSON en un archivo:
curl https://api.example.com/pedidos \
--request POST \
--header "Content-Type: application/json" \
--data @pedido.jsonAsí evitas llenar el comando de comillas y caracteres escapados y, de paso, queda bastante más fácil de leer.
En resumen: cURL es multiplataforma, pero los comandos pasan primero por el terminal. La petición es la misma; lo que cambia es la forma de escribirla, y si puedes utilizar el terminal de Bash en Windows ;).
Todo muy bonito, pero ¿de dónde sale el token?
Hasta ahora hemos partido de que ya teníamos un token o una cookie de sesión. Pero, claro, alguien tendrá que proporcionárnoslos.
En una aplicación sencilla puede haber un endpoint de login al que enviamos el usuario y la contraseña. Si las credenciales son correctas, el servidor devuelve un token —normalmente en el cuerpo de la respuesta— o crea una cookie de sesión mediante la cabecera Set-Cookie.
Pero no todos los sistemas de autenticación son tan directos. Una empresa puede tener un inicio de sesión único para varias aplicaciones, o delegar el acceso en un proveedor externo como Google o Microsoft Entra. En esos casos se suele utilizar OAuth 2.0 junto con OpenID Connect: la aplicación te lleva al proveedor de identidad, allí inicias sesión y, al terminar, vuelves a la aplicación con la autorización necesaria para acceder.
Es el típico "Entrar con Google" o el login corporativo que te pide la cuenta de Microsoft y el doble factor. Aquí ya no hay una simple petición con usuario y contraseña que podamos copiar en un comando: intervienen el navegador, varias redirecciones, códigos temporales y, finalmente, uno o varios tokens.
Ahora bien, OAuth no es una sola cosa. Según quién esté al otro lado, el flujo cambia, y con él lo que cURL puede hacer por ti.
Cuando se conectan dos servidores
Imagina que un proceso de tu backend necesita consultar otra API sin que haya un usuario delante. No hay formulario de acceso, navegador ni doble factor: una aplicación se identifica frente a otra.
Para estos casos se suele utilizar el flujo Client Credentials. El servidor envía su identificador, su secreto y los permisos que necesita al proveedor de identidad:
curl https://identity.example.com/oauth/token \
--request POST \
--header "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id=$CLIENT_ID" \
--data-urlencode "client_secret=$CLIENT_SECRET" \
--data-urlencode "scope=orders.read"El servidor de identidad responde con un access_token, que ya podemos utilizar para llamar a la API:
curl https://api.example.com/orders \
--header "Authorization: Bearer $ACCESS_TOKEN"Estos dos comandos no los puedes ejecutar tal cual:
identity.example.comyapi.example.comson dominios de ejemplo. Tendrás que sustituir las URLs, las credenciales y el scope por los de Microsoft Entra, Auth0, Keycloak o el proveedor que utilice tu aplicación.
Aquí cURL encaja perfectamente porque todo el proceso consiste en peticiones HTTP y no hace falta que nadie interactúe con una pantalla.
Cuando una persona tiene que iniciar sesión
Si para acceder hay una persona que debe iniciar sesión con Google, Microsoft Entra u otro proveedor, el proceso se complica. Puede haber formularios, redirecciones, un doble factor y otros pasos que necesitan la intervención del usuario.
Aunque es posible reproducir parte de ese flujo con cURL, para hacer una prueba puntual no merece la pena. Lo más sencillo es iniciar sesión normalmente desde el navegador y, una vez dentro, copiar el access_token que ya utiliza la aplicación.
Para encontrarlo, abre las herramientas de desarrollo del navegador, entra en la pestaña Network —o Red, si la tienes en español— y selecciona una petición a la API. Normalmente, el token aparecerá en la cabecera Authorization:
Authorization: Bearer eyJhbGciOi...Copia el valor que aparece después de Bearer, guárdalo temporalmente en una variable y úsalo en tus peticiones:
export ACCESS_TOKEN="eyJhbGciOi..."
curl https://api.example.com/orders \
--header "Authorization: Bearer $ACCESS_TOKEN"También puedes hacer clic con el botón derecho sobre la petición y seleccionar Copy as cURL. El navegador generará el comando completo con la URL y las cabeceras que se enviaron.
Puede que no encuentres ningún access_token. Algunas aplicaciones utilizan un BFF y mantienen la sesión mediante una cookie HttpOnly; en ese caso, tendrás que copiar la cookie como vimos en el apartado anterior.
En ambos casos estás copiando credenciales reales. Utilízalas para hacer pruebas puntuales, no las compartas y recuerda que dejarán de funcionar cuando caduque la sesión.
Cuando el login ocurre fuera del navegador
Este seguro que lo has sufrido: ejecutas algo como gh auth login, la herramienta te enseña una dirección web y un código corto, abres esa dirección en el navegador, te identificas y tecleas el código. Mientras tanto, la herramienta va preguntando al proveedor cada pocos segundos hasta que este le confirma que ya estás dentro y le entrega el token. Se llama Device Authorization Flow, y es también lo que hace tu televisión cuando entras en Netflix o YouTube y te pide ir a una web a meter cuatro letras.
Aquí cURL tampoco te va a resolver el login, pero el final es el de siempre: cuando el proceso termina hay un access_token, y a partir de ahí las peticiones vuelven a ser un --header "Authorization: Bearer ...".
Si tu API utiliza este flujo, la documentación debe explicar cómo iniciarlo y obtener el token. Una vez autenticado, puedes utilizar ese token en las peticiones con cURL como en los ejemplos anteriores.
¿Y cómo se lleva cURL con la IA?
De maravilla. Un comando cURL es texto: ocupa poco, se copia, se edita, se compara y se puede añadir perfectamente en una documentación que use formato Markdown. Para un modelo es mucho más fácil interpretar eso que adivinar qué hay repartido entre cuatro pantallazos de Postman. Y tampoco le estás pidiendo que sepa cómo se configura una herramienta concreta: en el propio comando están la URL, el método, las cabeceras, las cookies y el cuerpo.
Puedes pegarle un comando y pedirle cosas como estas:
- Explícame qué hace esta petición, o ayúdame a entender el error que devuelve la API.
- Conviértela a TypeScript, C#, Python o lo que use mi proyecto. O pásala de Bash a PowerShell.
- Añádele autenticación, una cabecera o un parámetro.
- Genera varios casos de prueba a partir de este ejemplo.
Y si trabajas con un agente que puede usar el terminal, la cosa sube de nivel: la herramienta puede ejecutar el comando, analizar la respuesta y ayudarte a corregirlo. Ya no tiene que interpretar una captura, puede probar exactamente la misma petición que probarías tú.
También funciona en la dirección contraria: le das la documentación de un endpoint y le pides un ejemplo con cURL. Eso sí, revísalo y ejecútalo antes de darlo por bueno. Que un modelo se sepa cURL de memoria no significa que conozca los detalles de tu API.
Y lo de siempre: no pegues tokens, cookies ni contraseñas reales en un chat. Sustitúyelos por
$ACCESS_TOKEN,$CLIENT_SECRETo$SESSION_COOKIE. La IA necesita conocer la forma de la petición, no tener acceso a tus credenciales.
Una captura de Postman es una imagen de una petición. Un comando cURL es la petición, escrita en un formato que entienden una persona, una IA y una terminal, ahí está la diferencia.
Entonces, ¿cómo documentamos una API?
cURL tampoco sustituye al resto de la documentación. Una API bien documentada debería combinar varias piezas:
- Una especificación OpenAPI.
- Swagger UI o una herramienta similar para consultar los endpoints.
- Una guía de inicio rápido escrita para humanos.
- Ejemplos con cURL para las operaciones más habituales.
- Una explicación completa de cómo funciona la autenticación y cómo se obtienen las credenciales.
- Ejemplos tanto de las respuestas correctas como de los errores más frecuentes.
- Una colección de Postman, si resulta útil, pero como complemento.
- SDKs cuando el tamaño o la complejidad de la API los justifiquen.
Dentro de ese conjunto, los ejemplos con cURL tienen una ventaja difícil de batir: no obligan a instalar una aplicación, crear una cuenta o elegir entre JavaScript, C#, Java y Python. Basta con abrir un terminal.
Además, como son texto plano, se pueden copiar, revisar en una pull request, guardar en Git, incluir en un script o pasar a otro lenguaje.
Una captura de Postman muestra cómo estaba configurada la petición en una interfaz. Un ejemplo con cURL expresa esa misma petición como texto ejecutable: puedes copiarlo, probarlo y adaptarlo sin tener que reconstruir nada.
La chuleta
Y, de postre, aquí tienes una chuleta para consultar rápidamente cómo se construye una petición con cURL: los parámetros más habituales, las diferencias entre Bash, PowerShell y CMD, y algunos atajos que te pueden ahorrar bastante tiempo.

Salvemos a los gatitos
Postman va a seguir siendo una herramienta magnífica, y yo voy a seguir utilizándola cuando me venga bien. El problema nunca fue Postman, sino pretender que unas cuantas capturas de pantalla sean documentación.
Describe el contrato con OpenAPI, explica el proceso con palabras y añade ejemplos de cURL que se puedan copiar, ejecutar y adaptar.
Le ahorrarás tiempo y unos cuantos quebraderos de cabeza a la siguiente persona que tenga que trabajar con tu API.
Y, de paso, salvaremos a algún gatito.

Referencias
- Documentación oficial de cURL
- Manual de cURL
- Guía de JSONPlaceholder
- httpbin: servicio para inspeccionar peticiones HTTP
- Postman: Newman no es compatible con el formato de colección v3
- RFC 7636: Proof Key for Code Exchange (PKCE)
- RFC 8628: OAuth 2.0 Device Authorization Grant
¿Necesitas ayuda con tu proyecto?
En Lemoncode llevamos años diseñando y desarrollando aplicaciones para proyectos reales, y por el camino nos hemos integrado con unas cuantas APIs de terceros. Algunas con una documentación estupenda. Otras con tres pantallazos de Postman.
Los desafíos que van más allá de este post ya los tenemos resueltos y rodados:
- Diseñar y documentar una API que otros van a consumir: contrato en OpenAPI y ejemplos que se puedan ejecutar
- Integraciones con servicios de terceros, incluidas las que llegan con documentación regular
- Autenticación y autorización: OAuth 2.0, OpenID Connect, SSO corporativo y arquitecturas con BFF
- Arquitectura front-end y full-stack, gestión de datos, caché y rendimiento
- Cómo encajar las herramientas de IA en el flujo del equipo sin perder el criterio
Si te toca arrancar una integración, publicar una API para terceros o arrancar un proyecto, podemos ayudarte.
Escríbenos a info@lemoncode.net y cuéntanos tu caso. Estaremos encantados de escucharte.