Реальні API мають операції, які погано виражаються через «створити, прочитати, оновити, видалити»: скасувати замовлення, опублікувати статтю, надіслати рахунок, перерахувати знижку.
Варіант 1 - зміна стану через PATCH:
PATCH /orders/42
{"status": "cancelled"}
Підходить, коли дія справді лише змінює поле. Проблеми починаються, коли:
- переходу потрібні додаткові дані (причина скасування, сума повернення);
- переход має побічні ефекти (повернення коштів, листи, звільнення товару на складі);
- дозволені не всі переходи (не можна скасувати відправлене замовлення).
Тоді PATCH з полем status ховає важливу бізнес-операцію за «оновленням поля», і валідація переходів розмазується.
Варіант 2 - дія як підресурс (найпоширеніший):
POST /orders/42/cancel {"reason": "Помилка в адресі"}
POST /articles/7/publish
POST /invoices/15/send
Дієслово в URL - свідомий виняток із «лише іменників»: операція явна, має власні параметри, права й журнал аудиту.
Варіант 3 - дія як ресурс-іменник:
POST /orders/42/cancellations # створити «скасування»
POST /refunds {"order_id": 42, "amount": 1000}
Корисно, коли у дії є власний життєвий цикл (повернення коштів може бути в обробці, відхиленим, завершеним) і її треба переглядати потім.
Google API (AIP-136) використовує синтаксис з двокрапкою: POST /orders/42:cancel. Він чітко відділяє дію від ієрархії ресурсів, але в Laravel-проєктах частіше бачать варіант 2.
Правила для власних дій:
- метод
POST- дія неідемпотентна чи має побічні ефекти. Для ідемпотентних (повторне «опублікувати» нічого не змінює) варто це задокументувати; - відповідь - оновлений ресурс (
200з тілом) або202 Accepted, якщо дія асинхронна; - недопустимий перехід -
409 Conflict(замовлення вже відправлено) з поясненням у тілі; - права - окремо для кожної дії (
can:cancel,order), а не загальне «можна оновлювати».
Головне - послідовність: один стиль для всіх дій в API.