← Блог

curl POST-запрос: как отправить JSON, заголовки и токен

Коротко: чтобы отправить JSON через curl, передайте его флагом -d и добавьте заголовок Content-Type: application/json. Писать -X POST не обязательно: раз есть тело, curl сам выберет POST.

curl https://playground.tetiva.app/post \
  -H "Content-Type: application/json" \
  -d '{"name": "Тетива", "qty": 2}'

Без заголовка curl отправит тело как данные формы, и многие API ответят 400 или 415. В curl 7.82 и новее оба шага заменяет один флаг --json.

Команду можно запустить прямо сейчас. Адрес ведёт на наш тестовый API: он возвращает запрос в том виде, в каком его получил сервер.

Три части команды. Обратная косая черта в конце строки переносит команду на следующую строку в bash и zsh

Из чего состоит команда#

Программа curl отправляет HTTP-запросы из терминала. Она есть в macOS и в Windows 10 и 11, а в Linux обычно уже установлена или ставится из пакетов.

Адрес. Его можно писать первым или последним, curl разберётся. Кавычки нужны, только если в адресе есть & или ?: иначе оболочка поймёт & по-своему и запустит команду в фоне.

Заголовок, -H. Пишется как "Имя: значение". Заголовков бывает сколько угодно, на каждый свой -H.

Тело, -d. Всё, что после него, уйдёт на сервер как есть. JSON берут в одинарные кавычки, чтобы двойные внутри дошли до curl нетронутыми. Это работает в macOS, Linux и Git Bash. В Windows правила другие, о них ниже.

Нужен ли -X POST#

С -d указывать -X POST не нужно. Если запустить curl -v -X POST -d ..., curl сам об этом напишет: Note: Unnecessary use of -X or --request, POST is already inferred.

-X нужен в двух случаях:

  • POST без тела, например чтобы перезапустить задачу. Без -X curl отправил бы GET.
  • Другие методы с телом: -X PUT -d '...' или -X PATCH -d '...'.
curl -X POST https://api.example.com/jobs/42/restart
curl -X DELETE https://api.example.com/orders/42

Для DELETE, как во второй строке, тело обычно не нужно.

GET-запрос в curl#

Команда curl с одним адресом отправляет GET и печатает тело ответа. Параметры пишут прямо в адресе, а сам адрес берут в кавычки из-за ? и &:

curl "https://playground.tetiva.app/get?page=2&limit=10"

Тестовый API вернёт параметры в поле args. Чтобы увидеть ещё код ответа и заголовки, добавьте -i.

Что видит сервер: JSON или форма#

Бывает, что тело до сервера дошло, но он не понял, что это JSON. В ответе тестового API это видно сразу: с заголовком данные попадают в поле json, без него в form.

Один и тот же -d. Без заголовка сервер принял JSON за поле формы, с Content-Type: application/json разобрал его как JSON

Если тело не разобралось, проверьте две вещи: есть ли заголовок и не сломались ли кавычки. Невалидный JSON вроде {name:Тетива} сервер тоже не прочитает.

У свежего curl есть сокращение --json. Оно кладёт данные в тело и само ставит Content-Type: application/json и Accept: application/json:

curl --json '{"name": "Тетива"}' https://playground.tetiva.app/post

Флаг появился в curl 7.82.0 весной 2022 года. Проверить версию можно командой curl --version. В старых сборках Windows 10 встроенный curl может оказаться старше.

POST с формой и файлом#

Если API ждёт обычную форму, а не JSON, заголовок не нужен: -d сам ставит Content-Type: application/x-www-form-urlencoded. Поля разделяют &:

curl https://playground.tetiva.app/post \
  -d 'name=Tetiva&qty=2'

Файл отправляют флагом -F. С ним curl собирает тело multipart/form-data, как браузер при загрузке файла, и тоже сам выбирает POST. @ перед именем значит «взять содержимое файла»:

curl https://playground.tetiva.app/post \
  -F 'file=@photo.jpg' \
  -F 'name=Tetiva'

Тестовый API вернёт поля в form, а файл в files.

Тело из файла#

Когда JSON длинный, в нём кириллица или много кавычек, проще положить его в файл и передать через @:

curl https://playground.tetiva.app/post \
  -H "Content-Type: application/json" \
  --data-binary @order.json

Почему --data-binary, а не -d @order.json: -d выбрасывает из файла переводы строк. JSON от этого не ломается, а вот текст, где переносы важны, сломается. --data-binary отправляет файл байт в байт. Вместо имени файла можно написать @-, и curl прочитает тело из стандартного ввода.

Если в сам JSON нужно подставить переменную оболочки, тело берут в двойные кавычки, а внутренние экранируют: -d "{\"name\": \"$NAME\"}". Если в значении могут оказаться кавычки, надёжнее собрать тело через jq и передать его curl через стандартный ввод:

jq -n --arg name "$NAME" '{name: $name}' \
  | curl --json @- https://playground.tetiva.app/post

Заголовки и токен#

Токен передают заголовком Authorization. Тестовый API проверяет его на /bearer:

curl https://playground.tetiva.app/bearer \
  -H "Authorization: Bearer abc123"

Без заголовка тот же адрес ответит 401 и напишет missing Authorization header. Если токен передан, а сервер его всё равно не принимает, загляните в статью про Bearer-токены.

Токен удобно держать в переменной окружения: TOKEN=abc123, а в команде писать -H "Authorization: Bearer $TOKEN". Кавычки здесь двойные: в одинарных оболочка не подставит переменную и отправит буквально $TOKEN.

Команда с токеном остаётся в истории терминала и в любом чате, куда её вставили. Как ключи утекают таким путём, рассказано в статье про токен на скриншоте.

Для логина и пароля есть -u: curl -u user:passwd https://playground.tetiva.app/basic-auth/user/passwd. curl сам соберёт заголовок Basic.

Чтобы увидеть, что ушло на самом деле, добавьте -v. Строки с > покажут отправленные заголовки, строки с < полученные. Флаг -i добавит заголовки ответа к выводу без остальных подробностей.

curl в Windows: cmd и PowerShell#

curl встроен в Windows 10 начиная с версии 1803 и в Windows 11, но команды для bash там не работают как есть. В cmd JSON берут в двойные кавычки, а внутренние экранируют \", строку переносят символом ^. В Windows PowerShell 5.1 вместо curl пишут curl.exe.

Командная строка (cmd). Одинарные кавычки cmd не понимает. JSON берут в двойные, а кавычки внутри экранируют обратной косой чертой. Строку переносят символом ^:

curl https://playground.tetiva.app/post ^
  -H "Content-Type: application/json" ^
  -d "{\"name\": \"Tetiva\", \"qty\": 2}"

PowerShell. В Windows PowerShell 5.1, который стоит в системе, curl означает вовсе не curl, а встроенную команду Invoke-WebRequest. Поэтому флаги curl вызывают ошибку про параметр Headers. Настоящий curl вызывается как curl.exe. Строки в PowerShell переносят обратным апострофом.

Кавычки внутри JSON PowerShell тоже может потерять по дороге к curl.exe, и тогда сервер получит сломанное тело. Если не хочется в этом разбираться, используйте файл: curl.exe ... --data-binary "@order.json". Он одинаково работает в cmd, PowerShell и bash. В PowerShell @order.json берут в кавычки, потому что @ там тоже имеет особое значение.

В PowerShell 7 псевдонима curl нет, и curl сразу запускает curl.exe, а с версии 7.3 JSON в одинарных кавычках доходит до него без потерь.

Одна и та же команда в трёх оболочках. Отличаются кавычки, перенос строки и имя самого curl

Частые ошибки#

Что видитеПочемуЧто сделать
415 Unsupported Media TypeНет Content-Type: application/jsonДобавьте -H или замените -d на --json
400 Bad Request, «invalid JSON»Оболочка съела кавычки в телеПоложите JSON в файл и отправьте через --data-binary @файл
Сервер получил форму вместо JSON-d без заголовкаДобавьте Content-Type
401 UnauthorizedНет заголовка Authorization, нет слова Bearer или токен истёкЗапустите с -v и посмотрите, что ушло
В cmd вторая строка команды выполняется отдельноПеренос \ работает только в bash и zshВ cmd переносите через ^ или пишите в одну строку
PowerShell ругается на параметр Headerscurl здесь значит Invoke-WebRequestПишите curl.exe

Если curl не дошёл до ответа вовсе и вернул код 6, 7, 28, 35 или 56, дело не в теле запроса, а в сети: как найти слой, на котором умер запрос.

Как перенести curl в Postman или другой клиент и обратно#

Готовую команду проще всего взять из браузера. Откройте DevTools (F12), вкладку Network, нажмите правой кнопкой на нужный запрос и выберите Copy → Copy as cURL. В Chrome для Windows есть два варианта, (cmd) и (bash): берите тот, что под вашу оболочку. В команде будут все заголовки и cookie, то есть ваша сессия. Если нужен не один запрос, а вся страница целиком, сохраните HAR-файл.

Если длинную команду неудобно править в терминале, её можно вставить в API-клиент. В Postman для этого есть Import, куда вставляют текст команды. В Tetiva команду вставляют прямо в адресную строку HTTP-запроса: метод, адрес, заголовки, тело и авторизация разложатся по вкладкам, а уведомление перечислит, что приехало. Если вставка затёрла заполненные поля, рядом будет кнопка Undo.

Команда вставлена в адресную строку: POST, один заголовок и тело уже на своих местах

Чтобы получить из запроса команду curl, выберите Copy as cURL в меню рядом с кнопкой Send. Переменные вида {{token}} в команде уже подставлены, вместе со значениями секретов. Прежде чем отправлять такую команду в чат, замените токен на заглушку. Подробности, включая то, как в команду попадают pre-скрипты и cookie, есть в документации по импорту и экспорту.

Попробуйте сами#

  1. Отправьте команду из начала статьи и найдите в ответе поле json со своими данными.
  2. Уберите -H "Content-Type: application/json" и посмотрите, как данные переедут в form.
  3. Отправьте запрос на /bearer без заголовка и с ним: должны прийти 401 и 200.
  4. Вставьте любую из команд в свой API-клиент и отправьте её оттуда.

Все адреса для тренировки, включая медленные ответы и любые коды ошибок, собраны на странице тестового API.