Два часа ночи. Разработчик подключает ваш API, у него горит релиз. Делает запрос и получает:

{ "error": "invalid_request" }

Всё. Что не так, почему, что делать — ни слова. Он лезет в документацию, которой нет или которая врёт, перебирает варианты наугад, теряет сорок минут и пишет в поддержку злое письмо. А мог бы подключить ваш API за пять минут.

Вот это и есть DX, опыт разработчика. И сломалось тут не в технике: запрос обработан, код вернулся. Сломан разговор с человеком на той стороне.

Ошибка не наказание, а подсказка

Главное, что я повторяю командам: сообщение об ошибке читает не машина, а уставший человек. И ему от вас нужно ровно три вещи. Что случилось. Почему. Что делать дальше.

Сравните. Было:

{ "error": "invalid_request" }

Стало:

{
  "type": "https://api.example.com/errors/missing-api-key",
  "title": "Ключ API не передан",
  "status": 401,
  "detail": "Заголовок Authorization пустой или в неверном формате.",
  "code": "missing_api_key",
  "action": "Передайте ключ так: Authorization: Bearer <ваш_ключ>. Ключ — в /dashboard/keys."
}

Второй вариант чинит себя сам. Разработчик прочитал, понял, поправил, поехал дальше. Без поддержки, без гаданий. Написать его — десять лишних минут, один раз. Эти десять минут экономят сотни часов вашим пользователям и вашей же поддержке.

Для нормального ответа изобретать формат заново не нужно — он уже стандартизирован. Есть RFC 9457 «Problem Details for HTTP APIs» (он же бывший 7807): ошибка отдаётся с медиа-типом application/problem+json и понятным скелетом полей. Берём этот скелет и достраиваем под живого читателя.

Посмотрите на структуру. Первые четыре поля — это скелет стандарта. type — URI типа ошибки, он же ведёт в доку, поэтому отдельное поле под ссылку не нужно. title — что произошло, по-человечески. status — HTTP-код. detail — почему именно в этом случае. Стандарт разрешает добавлять свои поля, и я добавляю два для уставшего человека: code — стабильный машинный код, по нему удобно ловить ошибку в обработке, и action — конкретный следующий шаг, а не «обратитесь к документации».

Тащить стандарт целиком никто не заставляет: если у вас уже есть свой формат, RFC прямо разрешает его оставить. Но опереться на скелет стоит — вы получаете ответ, который уже понимают чужие библиотеки и инструменты, плюс человеческую подсказку сверху. Готовый шаблон, прикручивайте ко всем ошибкам.

Время до первого «Hello World»

Вторая вещь, которой я меряю DX, — TTFHW. Время до первого успешного вызова. Сколько проходит от «зашёл на сайт» до «получил первый осмысленный ответ от API».

Это главная метрика онбординга. Больше пяти минут — уже проблема. И вот где она обычно прячется:

Регистрация на три экрана: подтверди почту, заполни профиль компании и только потом получи ключ. Каждый шаг до первого вызова — это налог. Берите его, только если без него правда никак.

Документация, где первый кусок кода появляется на пятом экране, после философии продукта. Разработчику в два часа ночи философия не нужна. Нужен код, который он скопирует и запустит.

Примеры, которые не работают как есть. С плейсхолдерами вместо реальных значений, без авторизации, с комментарием «тут добавьте свою логику» в самом важном месте. Это не пример, а домашнее задание.

Как выглядит нормальный старт

Вот что должно встречать разработчика на первой странице. Сразу, без прелюдий, рабочий вызов:

curl https://api.example.com/v1/ping \
  -H "Authorization: Bearer test_key_123"
{ "status": "ok", "message": "Авторизация прошла. Готовы к работе." }

Скопировал, вставил, получил понятный ответ. Через минуту после регистрации человек уже видит, что всё работает. Теперь он вам доверяет и готов читать дальше: SDK, сложные сценарии, ту самую философию. Но сначала дайте ему победу.

И покажите не только счастливый путь. Рядом сразу покажите, как выглядит ошибка. Тогда разработчик заранее знает, как ваш API разговаривает в плохой день, и не пугается, встретив её в бою.

Принцип наименьшего удивления

Под всем этим лежит одно правило: API не должен удивлять. Эндпоинт делает ровно то, что обещают его имя и документация. Одинаковый ввод — одинаковая форма ответа. Никаких скрытых побочных эффектов.

Если опытный разработчик лезет в доки, чтобы угадать, что делает ваш метод, дизайн уже проиграл. GET не меняет данные. DELETE /users без id не сносит всех пользователей. Коды статусов привычные. Поля называются одинаково по всему API, а не userId тут и user_id там.

Скучно? Да. Скучный и предсказуемый API — это комплимент. Значит, разработчик подключился и ни разу не выругался.

С чего начать прямо сейчас

Откройте свою документацию и засеките время — от первого экрана до первого работающего вызова. Больше пяти минут? Вот вам первая задача.

Потом возьмите три самые частые ошибки своего API и посмотрите, что они отдают. Если там голое «invalid request» без причины и следующего шага — перепишите по шаблону выше. Это самая дешёвая правка с самым заметным эффектом, какую я знаю в DX.

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