В списке совместимых изменений GitHub есть «Adding a response field» — добавление поля в ответ.
Teams MAY define backwards compatibility as their business needs require. For example, Azure defines the addition of a new JSON field in a response to be not backwards compatible.
Перевод: команды MAY определять обратную совместимость так, как требует их бизнес. Например, Azure считает добавление нового поля JSON в ответ обратно несовместимым изменением.
Документ Azure о breaking changes: добавление нового поля в ответ названо несовместимым. Документ помечен как устаревший и ссылается на актуальную политику. Клик — увеличить.
Поэтому политика совместимости записывается явно. В учебном проекте принято правило GitHub, и клиенты обязаны игнорировать неизвестные поля: docs/VERSIONING.md. Список breaking changes GitHub разобран на слайде 69.
105Четыре места для версии API
Слайд 71 показал версию в пути. У Microsoft внутри одной компании два разных решения.
GuidelinesGraph.md, строки 134–138, подсвечены GitHub по ссылке на строки: структура URL и «`version` can be v1.0 or beta». Клик — увеличить.
106Deprecation и Sunset в каждом ответе
Пометку deprecated в OpenAPI увидит тот, кто читает документацию. Заголовок в ответе увидит программа, которая всё ещё вызывает устаревшую операцию.
During the deprecation phase, the producer should add a Deprecation: <timestamp> (see RFC 9745 section 2) and - if also planned - a Sunset: <date-time> (see RFC 8594 section 3) header on each response affected by a deprecated element
Перевод: на этапе устаревания владелец API должен добавлять заголовок Deprecation со временем (RFC 9745, раздел 2) и, если отключение запланировано, заголовок Sunset с датой (RFC 8594, раздел 3) в каждый ответ, который затрагивает устаревший элемент.
Устаревшая операция: путь зачёркнут, в ответе 200 описаны заголовки Deprecation и Sunset. Клик — увеличить.
107429: одинаковое имя заголовка — разный смысл
Код 429 и Retry-After определены в RFC. Заголовки X-RateLimit-* стандартом не являются, и компании понимают их по-разному.
The response representations SHOULD include details explaining the condition, and MAY include a Retry-After header indicating how long to wait before making a new request.
Перевод: ответ SHOULD объяснять причину и MAY содержать заголовок Retry-After, который показывает, сколько ждать перед новым запросом.
X-RateLimit-Reset: The relative time in seconds when the rate limit window will be reset. Beware that this is different to Github and Twitter’s usage of a header with the same name which is using UTC epoch seconds instead.
Перевод: X-RateLimit-Reset — относительное время в секундах до сброса окна лимита. Учтите, что GitHub и Twitter используют заголовок с тем же именем иначе: там время в секундах UTC epoch.
GitHub Docs: x-ratelimit-reset — время сброса в секундах UTC epoch. Клик — увеличить.
108ETag и If-Match: не потерять чужую правку
Два оператора открыли одно обращение. Первый закрыл его, второй через минуту меняет описание по старой копии. Условный запрос не даёт второму затереть изменение первого.
The "If-Match" header field makes the request method conditional on the recipient origin server either having at least one current representation of the target resource, when the field value is "*", or having a current representation of the target resource that has an entity tag matching a member of the list of entity tags provided in the field value.
Перевод: заголовок If-Match делает метод запроса условным: сервер выполняет его, только если у ресурса есть текущее представление (при значении «*») или если entity tag текущего представления совпадает с одним из перечисленных в заголовке.
большинство endpoints отдают etag; 304 не расходует лимит
1. копия не изменилась — тело не передаётся
GET /api/v1/tickets/a8d04cb9-…
If-None-Match: "4fec2a976840743a"
HTTP/1.1 304
etag: "4fec2a976840743a"
2. обращение уже изменили — сервер отказывает
PATCH /api/v1/tickets/a8d04cb9-…
If-Match: "4fec2a976840743a" # версия до чужой правки
HTTP/1.1 412
content-type: application/problem+json
{
"title": "Precondition failed",
"detail": "Ticket was changed after the version in If-Match was read"
}
109Idempotency-Key: распространённая практика без стандарта
POST не идемпотентен. Если ответ потерялся в сети и клиент повторил запрос, появится второе обращение. Ключ идемпотентности позволяет серверу узнать повтор.
datatracker.ietf.orgСКРИНШОТ · 16.09.2026
Черновик draft-ietf-httpapi-idempotency-key-header: статус Expired Internet-Draft. Клик — увеличить.
If there is an attempt to reuse an idempotency key with a different request payload, the resource SHOULD reply with a HTTP 422 status code with body containing a link pointing to relevant documentation.
Перевод: при попытке повторно использовать ключ с другим телом запроса ресурс SHOULD ответить кодом 422 с телом, в котором есть ссылка на документацию.
POST /api/v1/tickets Idempotency-Key: 8e03978e-… {"title": "Не работает VPN"}
HTTP/1.1 201 id: 6dfeb4e3-…
POST /api/v1/tickets Idempotency-Key: 8e03978e-… {"title": "Не работает VPN"}
HTTP/1.1 201 id: 6dfeb4e3-… # тот же id, обращений по-прежнему 4
POST /api/v1/tickets Idempotency-Key: 8e03978e-… {"title": "Не работает почта"}
HTTP/1.1 422 "Idempotency-Key was already used with a different request body"
Длина ключа и срок хранения взяты из Stripe и Zalando, правило 230 ↗, код 422 — из черновика IETF.
110Идентификатор запроса: имя выбирает компания
Одна операция проходит через клиент, gateway и сервис. Общий идентификатор позволяет найти её записи во всех логах. Стандартного имени у этого заголовка нет.
If the caller provides this header the service **must** include this in their log entries to facilitate correlation of log entries for a single request. Because this header can be client-generated, it should not …
Перевод: если вызывающая сторона передала этот заголовок, сервис must записывать его в логи, чтобы связать записи одного запроса. Поскольку значение может сформировать клиент, оно не должно… (дальше Azure ограничивает его использование).
Добавили в контракт новый заголовок ответа — добавьте его в expose_headers, иначе браузерный клиент его не получит.
112OAuth 2.0 описывают вместе с сервером авторизации
OpenAPI позволяет описать OAuth 2.0 со схемами authorizationCode и clientCredentials. Но схема в контракте — обещание клиенту, и обещать можно только то, что работает.
Если описать в учебном контракте oauth2 без сервера авторизации, Swagger UI покажет кнопку входа, которая никуда не ведёт. Такое описание нарушает главное свойство контракта: он должен совпадать с поведением API.
Схему OAuth 2.0 добавляют в тот же pull request, что и подключение сервера авторизации.
113Что изменилось в учебном API 1.1.0
Все механизмы этой части добавлены в учебный проект как совместимое изменение: версия 1.0.0 стала 1.1.0.
CHANGELOG.md (сокращено)
## [1.1.0] - 2026-09-16
### Added
- Заголовок X-Request-Id в каждом ответе.
- ETag, 304 Not Modified на совпавший If-None-Match.
- If-Match в PATCH, ответ 412 при устаревшей версии.
- Idempotency-Key в POST /api/v1/tickets.
- Лимит операций записи: 429 с Retry-After, X-RateLimit-*.
- CORS с открытыми заголовками контракта.
- Поле type в Problem Details ведёт на страницы видов ошибок.
### Deprecated
- GET /api/v1/tickets/open — используйте GET /api/v1/tickets?status=open.
31 тест проходит, export_openapi.py --check подтверждает совпадение контракта с кодом, Spectral замечаний не выдаёт. Проверено 16 сентября 2026 года.
Swagger UI · updateTicketСКРИНШОТ · 16.09.2026
PATCH /api/v1/tickets/{ticket_id}: параметр If-Match в списке параметров операции. Клик — увеличить.