Конвенції
Ця сторінка описує конвенції, спільні для всіх ендпоінтів /api/v1: базову адресу, формат відповідей, заголовки, пагінацію, роботу з датами та фільтрами. Якщо для конкретного ендпоінта конвенцію не перевизначено в Довіднику ендпоінтів — діє те, що описано тут.
База та версії
Базова адреса API — https://smengo.com/api/v1. Версія закріплена в шляху: зворотно несумісні зміни виходитимуть як /api/v2, а не як правки поточної версії — наявні інтеграції не зламаються непомітно.
API доступний лише по HTTPS. Відповіді не кешуються: кожна відповідь позначена Cache-Control: private, no-store, бо дані прив'язані до конкретного API-ключа.
Машинозчитуваний опис API — спека OpenAPI 3.1 за адресою GET /api/v1/openapi.json: вона публічна і віддається без API-ключа. Спробувати запити інтерактивно можна в консолі на сторінці /developers.
Формат відповіді
Список ресурсів
Успішна відповідь для списку — об'єкт із data (масив) та meta.next_cursor:
{
"data": [
{ "id": "9c1a2e34-5678-4abc-9def-0123456789ab", "...": "..." }
],
"meta": { "next_cursor": "eyJrIjoiSXZhbm92YSIsImlkIjoiOWMxYS4uLiJ9" }
}
meta.next_cursor дорівнює null, якщо це остання сторінка.
Одиничний об'єкт
Ресурси, яких в організації рівно один (наприклад, GET /org), повертають об'єкт без meta:
{ "data": { "name": "Coffee & Co", "slug": "coffee-co", "timezone": "Europe/Kyiv" } }
Помилка
Усі помилки повертаються в єдиному envelope — незалежно від ендпоінта та причини:
{
"error": {
"code": "validation_error",
"message": "Invalid query parameters.",
"hint": "from must not be later than to.",
"docs_url": "https://smengo.com/api-docs/reference/errors#validation_error",
"request_id": "8f14e45f-9e05-4f1a-8a3e-3d2b1c0f9b1b"
}
}
Повний каталог кодів помилок і що з кожним робити — на сторінці Помилки. Текстових відповідей у API немає — лише JSON, завжди, включно з помилками.
Заголовки відповіді
X-Request-Id— uuid v4, присутній на всіх відповідях/api/v1, включно з помилками. Вказуйте його, звертаючись у підтримку — за ним знаходиться конкретний запит у логах.Cache-Control: private, no-store— відповіді не призначені для кешування ані браузером, ані проксі.
Пагінація
Списочні ендпоінти використовують cursor-пагінацію:
?limit=— ціле число1..200, за замовчуванням100.?cursor=— непрозорий base64url-рядок ізmeta.next_cursorпопередньої відповіді. Не збирайте його вручну — просто передавайте як є в наступний запит.- Порядок сортування фіксований для кожного ресурсу та завжди включає
idяк тай-брейкер (наприклад, співробітники сортуються заfull_name, id). Тай-брейкер гарантує, що сторінки не дублюють і не пропускають рядки за однакових значень основного поля сортування. - Разом із
cursorпередавайте ті самі решту query-параметрів (фільтри таlimit), що й на першій сторінці. Змінювати їх між сторінками не можна: курсор продовжить пагінацію вже в новому наборі фільтрів, не помітивши підміни. - Зіпсований курсор або курсор від іншого ресурсу (не той тип чи форма поля сортування) —
400 invalid_cursor. А от курсор, отриманий з іншими фільтрами того самого ресурсу, не детектується: пагінація просто продовжиться з тієї самої keyset-позиції вже в новому фільтрі. Тому не перевикористовуйте курсори між різними наборами фільтрів — змінивши фільтри, починайте пагінацію заново, безcursor.
curl -s "https://smengo.com/api/v1/employees?limit=50&cursor=eyJrIjoiSXZhbm92YSIsImlkIjoiOWMxYS4uLiJ9" \
-H "Authorization: Bearer smg_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
Обмеження частоти
Кожен API-ключ обмежений rate_limit_per_min запитів на хвилину — за замовчуванням 60, ліміт налаштовується індивідуально для конкретного ключа. Вікно ковзне (sliding window, у стилі Cloudflare): під час підрахунку поточної хвилини частково враховується частка хітів попередньої хвилини, тому різкий сплеск рівно на межі хвилини не дає змоги обійти ліміт подвоєною кількістю запитів.
Три заголовки присутні на кожній відповіді /api/v1, що пройшла автентифікацію, — і на успішних, і на помилках, включно з 429. На відповідях, які до лімітера не доходять, їх немає: 401, ранні 403 (автентифікація та скоуп), 404 невідомого шляху, 405, OPTIONS, а також 500.
| Заголовок | Значення |
|---|---|
X-RateLimit-Limit |
Ліміт ключа, запитів/хв. |
X-RateLimit-Remaining |
Скільки запитів залишилося в поточному вікні. |
X-RateLimit-Reset |
Unix-час (секунди) кінця поточного хвилинного вікна. |
X-RateLimit-Reset — це кінець поточного хвилинного вікна, а не момент повного обнулення ліміту: вікно ковзне і зважує хіти попередньої хвилини, тому одразу після Reset X-RateLimit-Remaining може залишатися нижчим за повний ліміт. За 429 орієнтуйтеся на Retry-After, а не лише на Reset.
За перевищення ліміту відповідь — 429 rate_limited з додатковим заголовком Retry-After (секунди до наступної спроби):
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1784625300
Retry-After: 42
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded.",
"docs_url": "https://smengo.com/api-docs/reference/errors#rate_limited",
"request_id": "8f14e45f-9e05-4f1a-8a3e-3d2b1c0f9b1b"
}
}
Докладніше про код помилки — в Помилках → rate_limited.
Запити, відхилені на етапі автентифікації або перевірки скоупу (401 unauthorized, 403 missing_scope, 403 subscription_inactive), ліміт не витрачають — рахуються лише запити, що дійшли до самого лімітера.
Дати та час
- Поля-дні (
entry_date,date,period,from,to) — форматYYYY-MM-DD, у таймзоні організації, не в UTC. - Інстанти (
check_in_at,created_at,updated_at, …) — ISO 8601 UTC із суфіксомZ:2026-07-01T09:00:00.000Z. - Таймзона організації доступна у відповіді
GET /org(полеtimezone).
Фільтри
Застосовується до всіх часових ресурсів: schedule-entries, check-ins, timesheets, demand-signals.
from,to—YYYY-MM-DD, у таймзоні організації, межі включно. Передаються лише парою: один без одного на будь-якому часовому ресурсі —400 validation_errorз підказкоюpass both from and to, or neither.fromне може бути пізніше заto; максимальний діапазон — 366 днів; порушення будь-якої умови —400 validation_errorіз поясненням уhint. Якщо параметри не передані — за замовчуванням використовується поточний місяць у таймзоні організації, тому запит взагалі без параметрів уже повертає змістовні дані. Дляtimesheetsfrom/toзіставляються заperiod— першим днем місяця, якому належить табельний запис.department_id— доступний скрізь, де в ресурсу є відділ (зокрема наemployees); фільтр — строга рівність. Виняток —demand-signals: рядки зdepartment_id = null(сигнал по всій точці цілком) повертаються лише тоді, коли фільтрdepartment_idне передано.employee_id— доступний наschedule-entries,check-ins,timesheets.- Невідомий
department_id/employee_id, зокрема той, що належить чужій організації, — не помилка: просто порожній список. Так тенант-ізоляція не дає змоги перебором перевірити існування чужих ідентифікаторів. from/toна ресурсі без часового виміру (org,departments,employees,shift-presets,status-types) —400 validation_error: зайвий параметр не ігнорується мовчки, а відхиляється явно.- Те саме правило діє для
department_id/employee_id: параметр, який ресурс не підтримує, — теж400 validation_error, а не тихий ігнор. АGET /orgвідхиляє будь-які лістинг-параметри (limit,cursor, фільтри) тим самим400 validation_error: це одиничний об'єкт, а не список.
Що далі
- Помилки — формат envelope і повний каталог кодів.
- Скоупи та права — яке право потрібне, щоб надати ключу доступ до кожного ресурсу.