Яку структуру має використовувати відповідь API
Головне: Голі обʼєкти чисті, доки не потрібні метадані; повні обгортки безпечні, але багатослівні. Для більшості проєктів використовуйте статус HTTP для результатів і стабільну обгортку для даних.

Коротко: виражайте результати кодами статусу 4xx/5xx, тримайте навантаження в { "data": ..., "meta": ... } і перемикайтеся на обгортку error у разі збою; цього достатньо для внутрішніх API, і лише публічні платформи з багатьма інтеграторами потребують JSON:API чи OpenAPI.
Спершу відкиньте найгірший варіант: завжди повертати 200 із результатом у полі code.
Три форми
A. Голий обʼєкт
{ "id": 1, "name": "demo", "price": 99 }
Чисто, і семантика HTTP робить роботу (404 — це 404). Але щойно потрібна пагінація, попередження чи id трасування, нема куди їх подіти без зміни контракту.
B. Стабільна обгортка
{
"data": { "id": 1, "name": "demo" },
"meta": { "requestId": "abc", "took": 12 }
}
Навантаження й метадані живуть окремо, тож нові метадані ніколи не переформовують data. Коштує одного рівня вкладеності.
C. Обгортка на все (уникайте)
{ "code": 0, "message": "success", "data": {...} }
Форма не гріх; гріх — практика. Статус 200 для кожного результату означає, що CDN, шлюзи, автоматичні вимикачі, політики повторів і дашборди всі бачать успіх. Кожна тривога тоді потребує власного розбору.
Рекомендація
Статус HTTP для результату, обгортка для навантаження.
{
"data": { "id": 1, "name": "demo" },
"meta": { "requestId": "req_8f2a", "took": 12 }
}
{
"error": {
"code": "ORDER_NOT_FOUND",
"message": "Order does not exist or was deleted",
"details": [{ "field": "id", "issue": "not_found" }]
},
"meta": { "requestId": "req_8f2a" }
}
Статус 4xx або 5xx; code стабільний і машинозчитуваний; message для людей; details для помилок форми на рівні полів.
Жорсткі правила
- Кінцеві точки списків мусять нести метадані пагінації. Без
totalчи курсора немає пейджера. - Порожні списки повертають
[], ніколиnull.null.map— повторюваний збій. - Часові мітки в ISO 8601 зі зміщенням. Дивіться який формат для часових міток.
- Одна конвенція найменування.
snake_caseабоcamelCase, конвертована один раз на межі. - Гроші як цілі дрібні одиниці або рядки.
99.99не точне в двійковій плаваючій точці. - Не змішуйте
nullз відсутністю. Дивітьсяnullчи пропустити поле.
Коли впроваджувати специфікацію
- Публічні платформи з багатьма інтеграторами: JSON:API або OpenAPI плюс спільна обгортка
- Внутрішні API однієї команди: обгортки вище достатньо
- Сильні контракти: спершу Schema, структури генеруються з неї
Специфікації заощаджують обговорення й коштують онбордингу. Для команди з пʼяти людей JSON:API зазвичай робота з відʼємним результатом.
Часті питання
Як обирати між голим обʼєктом і обгорткою?
Використовуйте обгортку, коли потрібні пагінація, попередження чи requestId, щоб meta могла рости, не переформовуючи data. Повертайте голий обʼєкт, лише коли метадані не потрібні й ви приймаєте, що додавання полів пізніше змінить контракт.
Чому API ніколи не має повертати 200 для кожного результату?
CDN, шлюзи, автоматичні вимикачі, політики повторів і дашборди всі читають статус HTTP, щоб вирішити, кешувати, повторювати чи піднімати тривогу. З 200 усюди вони трактують збої як успіхи, і кожна тривога потребує власного розбору бізнес-коду.
Чи мають кінцеві точки списків нести метадані пагінації?
Так. Без total чи курсора немає пейджера, а порожні списки мусять повертати [], а не null, інакше виклик .map на фронтенді ламається.