Curl-примеры в документации API: чек-лист, чтобы они работали
Документация API без работающих примеров — половина документации. Человек открывает страницу, видит curl-команду, копирует в терминал, получает ошибку. Поправил кавычки — снова ошибка. Поменял URL — снова. Через десять минут он закрывает доку и идёт читать чужой клиентский код, надеясь увидеть рабочий вызов.
Curl-примеры — самый универсальный способ показать запрос к API. Они работают везде: на Linux, macOS, Windows (с WSL или нативным curl), в CI, в трейнингах. Но писать их ленятся: копируют первое, что попалось, забывают экранировать, не проверяют. Разберу чек-лист, по которому пишутся curl-примеры в документации, чтобы они не разочаровывали через год после публикации.
Базовый шаблон, от которого отталкиваюсь
curl -X POST https://api.example.com/v1/users \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_TOKEN_HERE' \
-d '{
"email": "alice@example.com",
"name": "Alice"
}'
Что в нём правильного:
- Метод указан явно через
-X, даже если он POST по умолчанию для-d. - URL — полный, https, с версией API.
- Заголовки на отдельных строках через
\— копируется построчно при чтении. - JSON-тело отформатировано, не в одну строку.
- Плейсхолдер
YOUR_TOKEN_HERE— большими буквами, очевидно, что это плейсхолдер.
Пример короче, если запрос проще, но структура та же.
Кавычки: одинарные снаружи, двойные внутри
Самая частая ошибка в curl-примерах — путаница с кавычками. Bash, zsh, Windows cmd обрабатывают их по-разному.
Было (часто встречается):
curl -X POST https://api.example.com/v1/users \
-H "Content-Type: application/json" \
-d "{\"email\": \"alice@example.com\"}"
Двойные кавычки снаружи + экранированные двойные внутри. На bash работает, но читается ужасно, и при копировании в Windows cmd ломается.
Стало:
curl -X POST https://api.example.com/v1/users \
-H 'Content-Type: application/json' \
-d '{"email": "alice@example.com"}'
Одинарные снаружи — bash не интерпретирует содержимое, JSON остаётся как есть. Внутри JSON — двойные, как требует стандарт.
Правило: одинарные кавычки для всего, что не содержит переменных окружения. Если нужно подставить переменную — двойные снаружи и экранирование, но это исключение.
Подстановка переменных окружения
Когда в примере токен — стоит сразу показать, как его взять из переменной:
export API_TOKEN="your-actual-token"
curl -X GET https://api.example.com/v1/users \
-H "Authorization: Bearer $API_TOKEN"
Двойные кавычки тут нужны, чтобы bash подставил $API_TOKEN. Это сразу учит читателя правильному паттерну: токен не лежит в shell history открытым текстом.
Альтернатива — --header "Authorization: Bearer ${API_TOKEN:?token not set}". Bash при пустом значении упадёт с понятной ошибкой, а не отправит запрос с Bearer . Полезно в скриптах, в документации обычно избыточно.
JSON в теле: -d vs --data-raw vs @file
Три способа передать тело, и у каждого свои нюансы.
-d со строкой:
curl -d '{"key": "value"}' ...
Подходит для коротких тел. -d по умолчанию ставит Content-Type: application/x-www-form-urlencoded — поэтому всегда явно указывай -H 'Content-Type: application/json', иначе сервер примет JSON за форму.
-d @file.json:
curl -d @body.json -H 'Content-Type: application/json' ...
Удобно для длинных тел. Но в документации работает плохо — у читателя нет файла body.json. Используй для скриптов, не для примеров в доке.
--data-raw:
curl --data-raw '{"key": "value"}' ...
Отличается от -d тем, что не интерпретирует @ в начале строки как файл. Полезно, когда тело начинается с @. В обычных примерах не нужен.
Экранирование специальных символов
Если в значении есть $, !, обратный слэш — bash может это интерпретировать.
Безопасно:
curl -d '{"password": "P@ssw0rd!"}' ...
Одинарные кавычки выключают подстановку. Если по какой-то причине нужны двойные:
curl -d "{\"password\": \"P\\@ssw0rd\\!\"}" ...
Это уже минное поле. Лучше переписать через одинарные.
URL-параметры и пробелы
Параметры в URL надо URL-кодировать. Пробел — %20, амперсанд внутри значения — %26, и так далее. Это часто забывают.
Было:
curl 'https://api.example.com/search?q=hello world&limit=10'
Может работать, может не работать (зависит от того, как прокси обрабатывает пробелы). Гарантированно работает:
curl 'https://api.example.com/search?q=hello%20world&limit=10'
Альтернатива — флаг -G + --data-urlencode:
curl -G https://api.example.com/search \
--data-urlencode 'q=hello world' \
--data-urlencode 'limit=10'
Curl сам соберёт URL и закодирует значения. В документации первый вариант понятнее, второй — для скриптов с пользовательским вводом.
Что показывать в ответе
Хороший пример показывает не только запрос, но и что ожидать в ответе:
curl -X GET https://api.example.com/v1/users/42 \
-H 'Authorization: Bearer YOUR_TOKEN'
Ответ:
{
"id": 42,
"email": "alice@example.com",
"name": "Alice",
"created_at": "2026-05-15T10:30:00Z"
}
Без ответа читатель не знает, тот ли вызов. Тратит ещё минуту, парся документацию схемы. Покажи короткий пример — экономия очевидна.
Если ответ длинный — обрежь до первых полей и поставь ... в массивах:
{
"users": [
{ "id": 1, "email": "alice@example.com" },
{ "id": 2, "email": "bob@example.com" }
],
"total": 247,
"next_cursor": "eyJpZCI6Mn0="
}
Флаги, без которых не обойтись
-i— показать заголовки ответа. Полезно при дебагеX-Request-Id,X-RateLimit-*.-v— verbose, видно весь обмен. В примерах не показываю, но в FAQ упоминаю как способ дебага.-s— silent, убирает прогресс-бар. Незаменим в скриптах, в примерах необязателен.-S— show errors даже в silent. Используется вместе с-sв скриптах:-sS.-f— fail на HTTP-ошибках, curl возвращает ненулевой exit code. Для CI-скриптов в доке полезно.-o file.json— записать ответ в файл.-w '%{http_code}\n'— вывести HTTP-статус отдельно.
В простых примерах не нужны все. Покажи минимум для понимания, остальное — в отдельной странице «Troubleshooting».
Версии curl и совместимость
Современный curl 8.x поддерживает HTTP/2, HTTP/3, мощные опции для timeouts. Но в примерах ориентируйся на 7.x — он стоит на старых системах, в CI-образах, в дистрибутивах с длинной поддержкой.
Что точно работает в 7.x: -X, -H, -d, --data-urlencode, -G, -i, -o, -w, -f. Что не использовать в примерах: --json (появился в 7.82), сложные форматы -w '%{json}' (тоже свежий).
Если используешь свежие фичи — оговори это:
# Требует curl 7.82+
curl --json '{"key": "value"}' https://api.example.com/items
Windows: альтернативы и подводные камни
Windows-пользователи теперь с нативным curl, но с особенностями. Curl в PowerShell — это alias на Invoke-WebRequest в старых версиях, а не настоящий curl. В PowerShell 7+ alias убрали, но если читатель на старом — он копирует пример и получает странную ошибку.
Решения:
- Упомянуть в FAQ: «На Windows используйте
curl.exeявно, чтобы обойти alias». - Дать альтернативу для PowerShell для самых частых запросов.
- Не использовать backslash-продолжение строк, оно работает по-разному. Пример в одну длинную строку всегда работает.
Я обычно даю «one-liner» вариант рядом с многострочным:
curl -X POST https://api.example.com/v1/users -H 'Content-Type: application/json' -H 'Authorization: Bearer YOUR_TOKEN' -d '{"email":"alice@example.com","name":"Alice"}'
Тестирование примеров в CI
Самая частая боль через год после релиза — примеры устарели. URL поменялся, поле переименовали, токен-схема другая. Без CI-проверки пример становится дезинформацией.
Простой подход: в репозитории доков лежат .bash-скрипты с примерами. CI запускает их против тестового API и проверяет, что HTTP-статус ожидаемый. Можно через bats, можно простым shell-скриптом.
#!/usr/bin/env bash
set -euo pipefail
response=$(curl -sS -o /tmp/response.json -w '%{http_code}' \
-X POST "$API_URL/v1/users" \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TEST_TOKEN" \
-d '{"email":"test@example.com","name":"Test"}')
if [ "$response" != "201" ]; then
echo "Expected 201, got $response"
cat /tmp/response.json
exit 1
fi
echo "OK"
Полное соответствие документации и кода вытащить сложно, но базовая проверка «команды из доки выполняются» — лучше, чем полностью «на честном слове».
Многоязычные примеры
Curl — стандарт, но не для каждого читателя. Хорошая практика — показать тот же запрос на двух-трёх языках через табы:
import requests
response = requests.post(
'https://api.example.com/v1/users',
headers={'Authorization': f'Bearer {API_TOKEN}'},
json={'email': 'alice@example.com', 'name': 'Alice'},
)
print(response.json())
const response = await fetch('https://api.example.com/v1/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiToken}`,
},
body: JSON.stringify({
email: 'alice@example.com',
name: 'Alice',
}),
});
const data = await response.json();
Не давай примеры на 8 языках — это шум. Curl + Python + TypeScript покрывают 80% аудитории. Остальное — через сгенерированный SDK.
Чек-лист curl-примера в доке
- Метод указан явно через
-X. - URL полный, с https и версией API.
- Одинарные кавычки снаружи, двойные внутри JSON.
- Все нужные заголовки указаны (
Content-Type,Authorization, кастомные). - Плейсхолдеры в верхнем регистре (
YOUR_TOKEN, неyour-token). - JSON-тело отформатировано, не в одну строку.
- Если есть параметры с пробелами — URL-encoded или через
--data-urlencode. - Показан пример ответа, обрезанный если большой.
- Пример работает после копирования в bash без правок (кроме плейсхолдеров).
- В CI есть смоук-тест, который запускает пример раз в день/при релизе.
Curl-примеры — это первая точка контакта с API. Если они плохие, дальше документацию никто не читает: уже сложилось впечатление, что «у них всё кривое». Если хорошие — на их основе пишется первая интеграция, и доверие к остальной документации растёт. Времени на качественный пример уходит немного, окупается каждый раз, когда новый разработчик копирует и запускает без вопросов.