Перейти до вмісту
Sursa Developers

Інкрементна синхронізація з updated_since

Щоб тримати копію даних Sursa у власній системі, не треба щоразу читати все. Списки об’єктів, що мають updated_at, приймають updated_since і повертають лише об’єкти, змінені в цей момент або пізніше.

curl -G https://api.sursa.com/v1/cards \
  -H "Authorization: Bearer $SURSA_API_KEY" \
  --data-urlencode "updated_since=2026-10-23T00:00:00Z" \
  --data-urlencode "limit=200"

З updated_since список сортується за updated_at за зростанням, потім за id, і курсор продовжує в тому самому порядку. Тож ви читаєте зміни в тому порядку, в якому вони сталися, а межа сторінки ніколи не розділяє їх так, щоб якась загубилася.

updated_since приймають такі списки:

СписокЩо вважається зміною
GET /v1/cards, GET /v1/cards/countupdated_at картки.
GET /v1/groupsupdated_at групи: її назва або налаштування.
GET /v1/webhooksupdated_at вебхука: його налаштування та секрет, а не його доставки.
GET /v1/jobsupdated_at завдання: кожна зміна його статусу.
GET /v1/draftsupdated_at чернетки.
GET /v1/cards/{card_id}/threadsОстаннє створення чи редагування кореневого коментаря або будь-якої відповіді. Гілка приходить повністю.
GET /v1/folders, GET /v1/collections/{name}/foldersupdated_at папки. Папки, що відповідають умові, приходять пласким списком, див. нижче.

Колекції, користувачі й оголошення не мають updated_at, тож їхні списки за ним не фільтруються: вони короткі, тому читайте їх повністю під час кожного запуску. Час має містити зсув часового поясу, як-от Z або +02:00; будь-яке інше значення відхиляється, як і в GET /v1/cards.

З updated_since дерево папок уже не є деревом: folders кожної колекції містить змінені папки пласким списком, з порожнім children, а parent_id показує, де розташована кожна з них. GET /v1/folders тоді пропускає колекції без змінених папок.

Цикл синхронізації

  1. Під час першого запуску прочитайте весь список без updated_since і запам’ятайте момент, коли запуск почався.
  2. Під час кожного наступного запуску передавайте запам’ятований момент як updated_since, мінус невеликий запас, наприклад хвилину, і запам’ятовуйте новий час початку.
  3. Записуйте кожен об’єкт у своє сховище за його id, замінюючи попередню копію. Об’єкти, що через запас трапляються двічі, просто записуються ще раз.
  4. Запитуйте кожну наступну сторінку з курсором і тим самим updated_since. Курсор працює лише з фільтрами першої сторінки: без них запит повертає 400 з кодом api.cursor_invalid.

Якщо брати час початку запуску, а не час його завершення, зміну, зроблену під час запуску, гарантовано підхопить наступний.

GET /v1/cards повертає масив JSON, тож його наступна сторінка передається в заголовку X-Next-Cursor (див. Списки, що повертають масив):

const BASE = "https://api.sursa.com";
const headers = { Authorization: `Bearer ${process.env.SURSA_API_KEY}` };

async function syncCards(since, saveCard) {
  const startedAt = new Date();
  const params = new URLSearchParams({ limit: "200" });
  if (since) params.set("updated_since", new Date(since.getTime() - 60_000).toISOString());
  let query = params.toString();
  while (query) {
    const response = await fetch(`${BASE}/v1/cards?${query}`, { headers });
    if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
    for (const card of await response.json()) await saveCard(card); // write into your store by card.id
    const cursor = response.headers.get("X-Next-Cursor");
    if (cursor) params.set("cursor", cursor); // keep updated_since: the cursor needs the same filters
    query = cursor ? params.toString() : "";
  }
  return startedAt; // pass it as `since` next time
}

Видалення

Список повертає лише наявні об’єкти, тож він не може повідомити, що щось видалили. Видалення беріть зі стрічки подій:

curl -G https://api.sursa.com/v1/webhook-events \
  -H "Authorization: Bearer $SURSA_API_KEY" \
  --data-urlencode "type=card.deleted" \
  --data-urlencode "created_since=2026-10-23T00:00:00Z"

Для читання стрічки потрібен ключ із роллю admin та областю доступу webhooks:manage. Події зберігаються 30 днів. Якщо ваша синхронізація не запускалася довше, прочитайте весь список заново й видаліть зі свого сховища всі об’єкти, яких у ньому вже немає.

Експорт до сховища даних поєднує списки, updated_since і стрічку подій в одному завданні.