Перейти к содержанию
Sursa для разработчиков

Инкрементальная синхронизация с 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 и ленту событий в одно задание.