Версионирование REST API: URL, заголовок или query
5 мин чтения
Версионирование REST API это способ, которым API сообщает вызывающему, какой контракт использует запрос, чтобы сервер мог менять контракт, не ломая вызывающих, которые ещё не перешли. Версия может жить в трёх местах: в пути URL (/v2/orders), в заголовке запроса (Accept: application/vnd.acme.v2+json или собственный Api-Version: 2026-10-01) или в параметре запроса (/orders?version=2). Путь встречается чаще всего, потому что виден везде, заголовок самый чистый для URL, а строка запроса это вариант, о котором большинство команд жалеет. Что подходит, зависит от того, кто ваши вызывающие и как вы планируете сообщать им об изменениях.
| Где живёт версия | Пример | Кто видит | Кэшируется по умолчанию | Кто использует |
|---|---|---|---|---|
| Путь URL | GET /v2/orders | Каждый лог, ссылка, прокси и браузер | Да | Twitter, Shopify, большинство публичных API |
| Собственный заголовок | Api-Version: 2026-10-01 | Только клиенты, которые его задают | С Vary: Api-Version | Stripe (Stripe-Version), GitHub (X-GitHub-Api-Version) |
| Заголовок Accept | Accept: application/vnd.acme.v2+json | Только клиенты, которые его задают | С Vary: Accept | Медиатипы GitHub, некоторые корпоративные API |
| Параметр запроса | GET /orders?api-version=2 | Логи и ссылки, вперемешку с фильтрами | Да, но засоряет ключи кэша | REST API Azure |
| Без версии | GET /orders плюс только аддитивные изменения | Никто | Да | Внутренние API и API в стиле GraphQL |
Что такое v1 и v2 в REST API?
v1 и v2 это метки двух версий контракта API: те же ресурсы, доступные по разным путям, где v2 может переименовать поля, изменить типы, убрать endpoint или поменять форму ошибок, от которых зависят вызывающие v1. Запрос к /v1/orders продолжает получать старую форму, пока v1 поддерживается; запрос к /v2/orders получает новую. Номер это обещание совместимости, и именно обещание делает его полезным. Повышение номера на каждый релиз превращает метку в шум.
Большинству API новая мажорная версия нужна только для изменения, которое ломает существующих вызывающих. Добавление поля, нового endpoint или нового необязательного параметра может выйти под текущей версией, потому что хорошо написанный клиент игнорирует то, чего не знает. Само решение разбирает статья когда версионировать API и что делать вместо этого; эта статья о том, где версия живёт при версионировании REST API, когда вы уже решили её поднять.
Где должна быть версия REST API: в URL или в заголовке?
Ставьте её в URL, если среди ваших вызывающих есть люди, которые набирают запросы в браузере, копируют примеры из документации или читают логи; ставьте в заголовок, если хотите один стабильный URL на ресурс, а ваши вызывающие это SDK или код, который вы контролируете. Путь URL явный так, как ничто другое: тикет в поддержку со строкой запроса уже говорит вам версию. Заголовок нужно задавать на каждом запросе, он невидим в ссылке и легко теряется на прокси, поэтому API с версией в заголовке всегда определяют умолчание для запросов, которые её не присылают.
У подхода с заголовком есть одно настоящее преимущество помимо аккуратных URL: он отделяет идентичность ресурса от его представления, а именно так был спроектирован HTTP. RFC 9110 описывает согласование содержимого через Accept, и медиатип вроде application/vnd.github.v3+json это версия, выраженная таким способом. На практике чистота дизайна значит меньше, чем то, помнят ли ваши вызывающие отправить заголовок.
Как работает версионирование по дате?
Вместо v1 и v2 версия это дата, и каждое ломающее изменение получает новую дату. Вызывающий закрепляет дату, под которую интегрировался, обычно один раз на уровне аккаунта, и сохраняет это поведение, пока сам не решит перейти. Заголовок Stripe-Version у Stripe и X-GitHub-Api-Version у GitHub работают именно так.
Компромисс в реализации. Каждое ломающее изменение становится небольшим слоем преобразования между закреплённой датой и текущей формой, и сервер складывает их в стопку: запрос, закреплённый два года назад, проходит через каждое изменение с тех пор. Это тяжелее строить, чем вторую копию обработчиков, и это вознаграждает API, который меняется редко. Команды, выпускающие по ломающему изменению в месяц, заканчивают с десятками преобразований. Статья версионирование API Stripe смотрит на модель со стороны вызывающего, включая то, что должен нести changelog.
Почему версионирование через строку запроса не рекомендуют?
Потому что оно смешивает контракт с фильтрами. /orders?status=open&api-version=2 заставляет версию выглядеть необязательным уточнением, а не тем, что определяет форму каждого поля в ответе. К тому же её проще всего потерять случайно, поскольку строки запроса пересобираются клиентскими библиотеками, срезаются некоторыми прокси и правятся людьми вручную. Собственное руководство Microsoft по дизайну REST API документирует форму с параметром запроса, которую использует Azure, и именно экосистема сгенерированных SDK вокруг неё делает это рабочим; без такого инструментария вызывающий, забывший параметр, молча получает версию по умолчанию.
Что происходит со старой версией?
Она продолжает работать, с датой, когда остановится. Смысл версионирования в том, чтобы дать вызывающим переходить по своему графику, а это верно, только если старая версия остаётся доступной достаточно долго и им сообщают, когда она закончится. Руководство Google по версионированию API описывает жизненный цикл, который оно применяет к собственным API: стабильная версия поддерживается до опубликованной даты депрекации, а замена доступна в течение всего окна депрекации.
Объявление это часть, которую большинство команд недостраивает. Новый путь или заголовок полезен только вызывающим, которые знают, что он существует, знают, что изменилось, и знают, когда v1 исчезнет. Это три факта, и каждому нужно место: датированная запись в changelog API с различиями на уровне полей, уведомление о депрекации, которое называет дату окончания, и руководство по миграции, которое проводит по каждому изменению вызывающего, всё ещё сидящего на старой версии. Последний шаг разбирает статья вывод версии API из эксплуатации, включая заголовок Sunset, который позволяет клиенту узнать дату из каждого ответа.
Можно ли вообще обойтись без версионирования REST API?
Да, пока каждое изменение аддитивно. Добавляйте поля, никогда не удаляйте и не переименовывайте их; добавляйте endpoint, никогда не меняйте то, что возвращает существующий; принимайте новые необязательные параметры, никогда не меняйте значение старого. Многие внутренние API годами живут на одном этом правиле, и это правило, которому схемы GraphQL следуют по замыслу. Цена это контракт, который только растёт, с депрекированными полями, которые приходится держать, потому что кто-то где-то их всё ещё читает.
Когда аддитивное изменение невозможно, инструмент это версия. Выбор между путём, заголовком и датой решает, что видят вызывающие и как объявляется изменение; неизменным остаётся то, что изменение должно быть записано там, где вызывающий его прочитает. Для API с внешними вызывающими это место changelog, а что считается ломающим изменением, разбирает статья breaking changes.
FAQ
Какой способ версионирования REST API лучший? Мажорная версия в пути URL понятна шире всего и её труднее всего сделать неправильно, что делает её умолчанием для публичного API. Версия в заголовке держит URL стабильными и подходит API, управляемым через SDK; заголовок с датой подходит API, которые меняются редко и хотят, чтобы вызывающие закрепляли поведение.
Что такое v1 в URL API?
Это первая мажорная версия контракта API. Запросам к /v1/... обещана форма, задокументированная для версии 1, даже после появления /v2/ с другими полями или поведением.
Нужна ли новая версия API на каждое изменение? Нет. Аддитивные изменения, такие как новые поля, endpoint и необязательные параметры, могут выйти под существующей версией. Новая нужна только изменению, которое ломает существующих вызывающих.
Как долго поддерживать старую версию API? Достаточно долго, чтобы вызывающие успели мигрировать, что для публичного API обычно шесть-двенадцать месяцев после появления замены, с датой, опубликованной при объявлении и повторённой в уведомлении о депрекации.
Технические утверждения в этой статье не проходили независимую проверку. Если здесь что-то не так, сообщите нам, и мы исправим.