Конвенції

Ця сторінка описує конвенції, спільні для всіх ендпоінтів /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, toYYYY-MM-DD, у таймзоні організації, межі включно. Передаються лише парою: один без одного на будь-якому часовому ресурсі — 400 validation_error з підказкою pass both from and to, or neither. from не може бути пізніше за to; максимальний діапазон — 366 днів; порушення будь-якої умови — 400 validation_error із поясненням у hint. Якщо параметри не передані — за замовчуванням використовується поточний місяць у таймзоні організації, тому запит взагалі без параметрів уже повертає змістовні дані. Для timesheets from/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 і повний каталог кодів.
  • Скоупи та права — яке право потрібне, щоб надати ключу доступ до кожного ресурсу.
Чи була стаття корисною?