На головну

API для розробників

Доступ до календаря змагань та реєстру учасників e-phan у форматі JSON, лише на читання. Ключ не потрібен — це ті самі записи, які платформа показує користувачам, і вони оновлюються одразу після зміни.

Базова адреса

https://api.e-phan.com/api/v1

Як підключитися

  1. 1

    Надішліть GET

    Без ключа, без реєстрації, без заголовків. GET /competitions?period=upcoming повертає JSON через HTTPS.

  2. 2

    Обмежте запит

    Поєднуйте країну, місто, період, пошук і діапазон дат як завгодно; будь-яке значення поза списком дозволених дає 400, а не мовчазне ігнорування.

  3. 3

    Перевіряйте, а не перезавантажуйте

    Збережіть ETag і поверніть його в If-None-Match. Незмінені дані відповідають 304 без тіла — це економить трафік, але не ліміт запитів.

Доступ

Без автентифікації. /api/v1 приймає анонімні GET через HTTPS: нічого отримувати й нічого надсилати. Ендпоїнти віддають опублікований календар і реєстр, які сайт і так показує анонімним відвідувачам, тож тут немає ані персональних даних, ані облікових даних, які треба захищати.

Ліміт рахується за адресою відправника, тож виклики з браузера витрачають окремий бюджет для кожного відвідувача, а серверна інтеграція — один бюджет на весь свій трафік. Серверний варіант усе одно робить значно менше запитів, бо одна закешована відповідь обслуговує всіх відвідувачів — і це єдиний варіант, якщо вам потрібна індексована розмітка, запасний варіант на час нашої недоступності або щоб ваші відвідувачі не потрапляли до наших логів.

Приклади коду

curl "https://api.e-phan.com/api/v1/competitions?period=upcoming&country=UA&limit=10"

Реєстр: стани й статуси

Кожен танцюрист, тренер, суддя та клуб має два незалежні поля. registryState показує, де саме перебуває запис; status — чи сплачено внески. Вони змінюються незалежно одне від одного, тож якщо читати лише одне, висновок буде хибним.

Щоб дізнатися, хто є учасником сьогодні, читайте registryState, а не status. Танцюрист із простроченим внеском залишається в реєстрі на весь пільговий період, тож поєднання status: "INACTIVE" і registryState: "REGISTRY" — це норма, а не суперечність.

registryState — де перебуває запис

Є в кожного танцюриста, тренера, судді та клубу; приймається як фільтр в усіх чотирьох списках.

  • REGISTRY

    В основному списку. Сюди входять і ті, у кого внесок прострочено, але пільговий період ще триває, тож наявність у реєстрі ще не означає сплачений внесок. Тренери та клуби не купують членства — їхнє місце дають прикріплені до них учасники, і з втратою останнього вони зникають з цього API повністю, доки не здобудуть нового або доки їх не забере архів.

  • ARCHIVE

    Окремий розділ реєстру, а не видалення. Архівні записи зберігають свої ідентифікатори й лишаються доступними, але ніколи не потрапляють у список, якщо їх не запитати явно.

status — чи сплачено внески

Зберігається окремо для кожної ролі, а не для людини: одна й та сама особа може бути ACTIVE як суддя й INACTIVE як танцюрист, і кожен ендпоїнт віддає статус саме тієї ролі, яку показує. Термін членства триває задану кількість місяців від дня оплати, тож membershipExpiresAt не припадає на якусь фіксовану дату — читайте його, а не обчислюйте. Тренери й клуби нічого не купують і власного статусу не мають зовсім: /coaches і /clubs не віддають поле status і не приймають ?status= — чи є вони в реєстрі, каже registryState.

  • ACTIVE

    Внесок сплачено, термін ще триває.

  • LIMITED

    Так само сплачене членство, дешевше за ACTIVE. Різниця в допуску: LIMITED виступає на рівнях 1–3, ACTIVE — на будь-якому. Це окреме значення, а не відтінок ACTIVE чи INACTIVE.

  • INACTIVE

    Сплачений термін вичерпано. Запис лишається в реєстрі до кінця пільгового періоду; registryUntil — це день, коли його забере архів.

Як читати архів

Архівування — це те, завдяки чому реєстр лишається списком нинішніх учасників, не втрачаючи історії. Записи зникають з основного списку, але зберігають усе інше.

  • За замовчуванням списки віддають registryState=REGISTRY. Архів ніколи не домішується — передайте registryState=ARCHIVE, щоб прочитати його як окремий розділ, або перелічіть обидва стани, щоб об’єднати їх.
  • Лічильники з /registry/filters обмежені так само, як і списки, тож фасет ніколи не пообіцяє записів, яких список не поверне. Відповідь прямо вказує цю межу в countsScope. Щоб оцінити розмір архіву, гортайте список із registryState=ARCHIVE.
  • Архівування ролі закриває її зв’язки з клубом і тренером. Рядки лишаються як історія, але dancersCount і coachesCount клубу перестають їх рахувати від дня закриття.
  • Повернення з архіву потребує платного поновлення, тож не очікуйте, що запис повернеться сам. archivedAt містить дату, коли він туди потрапив.

Як запитати архів

# Listings return the registry. The archive is not in them.
curl "https://api.e-phan.com/api/v1/dancers?limit=20"

# Ask for the archive by name to read it as its own section.
curl "https://api.e-phan.com/api/v1/dancers?registryState=ARCHIVE&limit=20"

# Or merge the two, if you are reconciling a full history.
curl "https://api.e-phan.com/api/v1/dancers?registryState=REGISTRY,ARCHIVE&limit=20"

Довідник ендпоїнтів

Усі ендпоїнти працюють лише на читання й повертають JSON. Фільтри можна поєднувати як завгодно: вони застосовуються через AND.

Змагання

GET/api/v1/competitions

Посторінковий список змагань. Чернетки організаторів ніколи не повертаються.

  • searchstring

    Шукає за назвою, містом, країною, місцем проведення та описом. Щонайменше 2 символи.

    Приклад: search=kyiv open

  • countrystring

    Назва країни або код ISO 3166-1 alpha-2.

    Приклад: country=UA

  • countryIdinteger

    Внутрішній id країни, див. /filters.

    Приклад: countryId=804

  • citystring

    Назва міста, збіг за початком рядка, без урахування регістру.

    Приклад: city=Kyiv

  • statusopen | completed

    Статус змагання. Кілька значень через кому.

    Приклад: status=open

  • typestring

    Ранг змагання за кодом. Кілька значень через кому; актуальний перелік див. у /filters.

    Приклад: type=ranking_tournament

  • registrationStatusopen | closed

    Стан реєстрації, який задав організатор.

    Приклад: registrationStatus=open

  • registrationOpenboolean

    true залишає лише змагання, які приймають заявки просто зараз, — те саме, що registration.state == "open". І день відкриття, і день дедлайну вважаються відкритими.

    Приклад: registrationOpen=true

  • periodupcoming | ongoing | past

    Період за календарним днем. Змагання, що триває сьогодні, вважається поточним увесь день.

    Приклад: period=upcoming

  • dateFromdate

    Календарний день включно. Залишає змагання, які перетинаються з діапазоном.

    Приклад: dateFrom=2026-09-01

  • dateTodate

    Календарний день включно. Залишає змагання, які перетинаються з діапазоном.

    Приклад: dateTo=2026-12-31

  • updatedSincedate-time

    Лише змагання, змінені після цієї позначки часу. Для інкрементної синхронізації; такі відповіді не кешуються.

    Приклад: updatedSince=2026-07-01T00:00:00Z

  • includecategories, entriesCount

    Додаткові дані у відповіді. Кілька значень через кому.

    Приклад: include=categories

  • sortstartDate | endDate | name | createdAt | updatedAt

    Поле сортування.

    Приклад: sort=startDate

  • orderasc | desc

    Напрямок сортування.

    Приклад: order=asc

  • pageinteger

    Номер сторінки, починаючи з 1.

    Приклад: page=1

  • limitinteger, 1-100

    Записів на сторінку, від 1 до 100. Значення поза діапазоном дає 400, а не мовчазне обрізання.

    Приклад: limit=20

GET/api/v1/competitions/{idOrSlug}

Одне змагання з повним списком категорій, поточними лічильниками та даними організатора. Приймає як id, так і slug.

GET/api/v1/competitions/{idOrSlug}/categories

Категорії одного змагання: вікові групи, класи, програми, танці в кожній та кількість виходів. Приймає як id, так і slug.

  • entryTypecouple | solo | duo-synchro | mix | show

    Тип виступу. Кілька значень через кому.

    Приклад: entryType=couple

  • stylenone | latin | standard | combined

    Програма категорії.

    Приклад: style=latin

  • levelinteger, 1-10

    Клас (рівень) категорії.

    Приклад: level=4

  • searchstring

    Шукає за назвою, містом, країною, місцем проведення та описом. Щонайменше 2 символи.

    Приклад: search=kyiv open

GET/api/v1/filters

Значення для фільтрів: країни й міста, де зараз проводяться змагання, а також допустимі значення переліків.

Реєстр

GET/api/v1/dancers

Посторінковий список танцюристів, які зараз у реєстрі. Архівні не потрапляють у видачу, якщо їх не запитати через registryState.

  • searchstring

    Пошук за іменем і прізвищем без урахування регістру (обидва варіанти написання). Щонайменше 2 символи.

    Приклад: search=Bohdan

  • sexMALE | FEMALE

    Стать: MALE або FEMALE.

    Приклад: sex=FEMALE

  • countryIdinteger

    Внутрішній id країни, див. /filters.

    Приклад: countryId=804

  • countrystring

    Назва країни або код ISO 3166-1 alpha-2.

    Приклад: country=UA

  • clubIduuid

    UUID клубу. Враховує лише чинне членство — зв’язки, закриті архівуванням, не рахуються.

    Приклад: clubId=0f1c7a2e-...

  • coachIduuid

    UUID тренера. Враховує лише чинне членство.

    Приклад: coachId=0f1c7a2e-...

  • danceClassinteger

    Танцювальний клас.

    Приклад: danceClass=3

  • hasPartnerboolean

    Фільтр за наявністю партнера: true або false.

    Приклад: hasPartner=true

  • registryStateREGISTRY | ARCHIVE

    Де перебуває запис: REGISTRY або ARCHIVE, кілька значень через кому. За замовчуванням REGISTRY — архів повертається, лише якщо вказати його тут явно. Будь-яке інше значення відхиляється.

    Приклад: registryState=ARCHIVE

  • statusACTIVE | LIMITED | INACTIVE

    Статус членства саме цієї ролі. Кілька значень через кому. Звіряється по ролі, тож людина з простроченим внеском в іншій ролі все одно потрапить у видачу. Не пов’язаний із registryState.

    Приклад: status=ACTIVE

  • sortname | approvedAt

    Поле сортування реєстру: name або approvedAt.

    Приклад: sort=name

  • orderasc | desc

    Напрямок сортування.

    Приклад: order=asc

  • pageinteger

    Номер сторінки, починаючи з 1.

    Приклад: page=1

  • limitinteger, 1-100

    Записів на сторінку, від 1 до 100. Значення поза діапазоном дає 400, а не мовчазне обрізання.

    Приклад: limit=20

GET/api/v1/coaches

Посторінковий список тренерів, які зараз у реєстрі: тренер лишається в списку, доки має принаймні одного танцюриста з реєстру. Архівні не потрапляють у видачу, якщо їх не запитати через registryState.

  • searchstring

    Пошук за іменем і прізвищем без урахування регістру (обидва варіанти написання). Щонайменше 2 символи.

    Приклад: search=Bohdan

  • sexMALE | FEMALE

    Стать: MALE або FEMALE.

    Приклад: sex=FEMALE

  • countryIdinteger

    Внутрішній id країни, див. /filters.

    Приклад: countryId=804

  • countrystring

    Назва країни або код ISO 3166-1 alpha-2.

    Приклад: country=UA

  • clubIduuid

    UUID клубу. Враховує лише чинне членство — зв’язки, закриті архівуванням, не рахуються.

    Приклад: clubId=0f1c7a2e-...

  • highestClassinteger

    Найвищий танцювальний клас.

    Приклад: highestClass=5

  • registryStateREGISTRY | ARCHIVE

    Де перебуває запис: REGISTRY або ARCHIVE, кілька значень через кому. За замовчуванням REGISTRY — архів повертається, лише якщо вказати його тут явно. Будь-яке інше значення відхиляється.

    Приклад: registryState=ARCHIVE

  • sortname | approvedAt

    Поле сортування реєстру: name або approvedAt.

    Приклад: sort=name

  • orderasc | desc

    Напрямок сортування.

    Приклад: order=asc

  • pageinteger

    Номер сторінки, починаючи з 1.

    Приклад: page=1

  • limitinteger, 1-100

    Записів на сторінку, від 1 до 100. Значення поза діапазоном дає 400, а не мовчазне обрізання.

    Приклад: limit=20

GET/api/v1/judges

Посторінковий список суддів, які зараз у реєстрі. Суддівський стан не залежить від танцювального запису тієї самої людини. Архівні не потрапляють у видачу, якщо їх не запитати через registryState.

  • searchstring

    Пошук за іменем і прізвищем без урахування регістру (обидва варіанти написання). Щонайменше 2 символи.

    Приклад: search=Bohdan

  • sexMALE | FEMALE

    Стать: MALE або FEMALE.

    Приклад: sex=FEMALE

  • countryIdinteger

    Внутрішній id країни, див. /filters.

    Приклад: countryId=804

  • countrystring

    Назва країни або код ISO 3166-1 alpha-2.

    Приклад: country=UA

  • judgeCategoryinteger

    Кваліфікаційна категорія судді.

    Приклад: judgeCategory=2

  • positionjudge | headJudge | sportsInspector

    Позиція: judge, headJudge або sportsInspector.

    Приклад: position=judge

  • registryStateREGISTRY | ARCHIVE

    Де перебуває запис: REGISTRY або ARCHIVE, кілька значень через кому. За замовчуванням REGISTRY — архів повертається, лише якщо вказати його тут явно. Будь-яке інше значення відхиляється.

    Приклад: registryState=ARCHIVE

  • statusACTIVE | LIMITED | INACTIVE

    Статус членства саме цієї ролі. Кілька значень через кому. Звіряється по ролі, тож людина з простроченим внеском в іншій ролі все одно потрапить у видачу. Не пов’язаний із registryState.

    Приклад: status=ACTIVE

  • sortname | approvedAt

    Поле сортування реєстру: name або approvedAt.

    Приклад: sort=name

  • orderasc | desc

    Напрямок сортування.

    Приклад: order=asc

  • pageinteger

    Номер сторінки, починаючи з 1.

    Приклад: page=1

  • limitinteger, 1-100

    Записів на сторінку, від 1 до 100. Значення поза діапазоном дає 400, а не мовчазне обрізання.

    Приклад: limit=20

GET/api/v1/clubs

Посторінковий список клубів, які зараз у реєстрі: клуб лишається в списку, доки має принаймні одного учасника з реєстру. dancersCount і coachesCount показують поточний склад.

  • searchstring

    Пошук за назвою клубу без урахування регістру. Щонайменше 2 символи.

    Приклад: search=Diamant

  • countryIdinteger

    Внутрішній id країни, див. /filters.

    Приклад: countryId=804

  • countrystring

    Назва країни або код ISO 3166-1 alpha-2.

    Приклад: country=UA

  • citystring

    Назва міста, збіг за початком рядка, без урахування регістру.

    Приклад: city=Kyiv

  • registryStateREGISTRY | ARCHIVE

    Де перебуває запис: REGISTRY або ARCHIVE, кілька значень через кому. За замовчуванням REGISTRY — архів повертається, лише якщо вказати його тут явно. Будь-яке інше значення відхиляється.

    Приклад: registryState=ARCHIVE

  • sortname | approvedAt

    Поле сортування реєстру: name або approvedAt.

    Приклад: sort=name

  • orderasc | desc

    Напрямок сортування.

    Приклад: order=asc

  • pageinteger

    Номер сторінки, починаючи з 1.

    Приклад: page=1

  • limitinteger, 1-100

    Записів на сторінку, від 1 до 100. Значення поза діапазоном дає 400, а не мовчазне обрізання.

    Приклад: limit=20

GET/api/v1/clubs/{id}

Один клуб повністю: дата заснування, публічні контакти, усі адреси та тренерський склад. Зроблено під сторінку клубу. Архівні клуби тут теж читаються — архів це розділ реєстру, а не видалення, — тож перевіряйте `registryState`, а не розраховуйте на 404. Танцюристів у відповіді немає, бо їх можуть бути сотні: за ними йдіть за `links.dancers`.

GET/api/v1/registry/filters

Значення для фільтрів реєстру: країни з лічильниками, класи, суддівські категорії та позиції, а також допустимі значення status і registryState. Лічильники обмежені так само, як і списки.

Формат відповіді

Списки приходять у data, поряд із meta зі станом пагінації та links із готовими адресами наступної й попередньої сторінок.

  • Кожне змагання має і id, і slug. Slug призначається один раз і фіксується в момент публікації, тож він переживає перейменування — будуйте свої адреси на ньому, а в /competitions/{idOrSlug} можна передати будь-який з двох.
  • registration.state має одне зі значень not_yet_open, open або closed. Воно замінило булеве isOpen, яке позначало як відкрите й те змагання, реєстрація на яке ще не почалася, бо булеве значення не здатне виразити три стани.
  • entriesCount і startsCount рахують різні речі. Одна заявка може обрати кілька категорій, тож сума startsCount по категоріях перевищує entriesCount змагання. Використовуйте entriesCount для «скільки взяло участь», а startsCount — для «наскільки заповнена категорія».
  • banner.horizontal і banner.vertical містять або адаптивний srcset за шириною в пікселях, або один запис default для банера, завантаженого до появи адаптивних розмірів, або null, якщо для цієї орієнтації нічого не завантажували.
  • Записи реєстру містять registryState поряд зі status, а також registryUntil — найближчу дату на відліку цього запису, хоч би який це був відлік, — і archivedAt, який дорівнює null для всього, що зараз не в архіві.
  • Клуб може мати кілька залів. У `locations` є всі адреси — спочатку головна, далі кожна філія зі своїм містом, країною та ідентифікатором місця Google. Поля `address` і `country` на верхньому рівні описують лише головну адресу й лишені пласкими для тих, хто підключився до появи філій. Фільтри `city`, `country` і `countryId` теж дивляться лише на головну адресу, тож клуб, у якого в місті лише філія, у видачу не потрапить.
  • Дати змагання та дедлайн реєстрації — це календарні дні у форматі YYYY-MM-DD, без часу й часового поясу, бо саме так їх задає організатор. createdAt і updatedAt — повні позначки часу.
  • Нові поля можуть з’являтися без попередження, тож ваш клієнт має ігнорувати незнайомі ключі.

GET /api/v1/competitions

{
  "data": [
    {
      "id": "0f1c7a2e-9d64-4a1b-bb02-7e9f8c3d5a11",
      "slug": "kyiv-open-2026",
      "name": "Kyiv Open Championship 2026",
      "shortName": "Kyiv Open 2026",
      "status": "open",
      "type": { "code": "ranking_tournament", "label": "Ranking tournament" },
      "organizer": {
        "name": "Ukrainian Dance Federation",
        "shortName": "UDF",
        "country": "Ukraine",
        "countryCode": "UA",
        "website": "https://example.org"
      },
      "resultsUrl": null,
      "dates": { "start": "2026-10-17", "end": "2026-10-18" },
      "location": {
        "country": "Ukraine",
        "countryCode": "UA",
        "city": "Kyiv",
        "venueName": "Palace of Sports",
        "venueAddress": "1 Sportyvna Sq."
      },
      "registration": {
        "status": "open",
        "state": "open",
        "opensAt": "2026-03-01",
        "deadline": "2026-10-10",
        "maxEntries": 800,
        "entriesCount": 317,
        "startsCount": 894
      },
      "banner": {
        "horizontal": {
          "640": "https://api.e-phan.com/uploads/competitions/kyiv-open-640.avif",
          "1280": "https://api.e-phan.com/uploads/competitions/kyiv-open-1280.avif",
          "1920": "https://api.e-phan.com/uploads/competitions/kyiv-open-1920.avif"
        },
        "vertical": null
      },
      "media": {
        "entryForm": null
      },
      "categories": [
        {
          "id": "6b8d1f04-2c77-4f1e-9a3b-5d2e0c8a4477",
          "name": "Juvenile I Latin, class E",
          "entryType": "couple",
          "style": "latin",
          "ageGroup": "Juvenile I",
          "level": 5,
          "dances": ["samba", "cha-cha-cha", "jive"],
          "startsCount": 42
        }
      ],
      "links": {
        "self": "…",
        "categories": "…",
        "web": "…",
        "registration": "…"
      },
      "updatedAt": "2026-07-21T10:12:44.907Z"
    }
  ],
  "meta": { "page": 1, "limit": 20, "total": 34, "totalPages": 2, "hasNextPage": true },
  "links": { "self": "…", "next": "…", "previous": null }
}

GET /api/v1/dancers

{
  "data": [
    {
      "id": "a3f2c1d8-7e4b-49a1-bc06-3d5e9f1a2b77",
      "name": "Олена",
      "surname": "Петренко",
      "nameEn": "Olena",
      "surnameEn": "Petrenko",
      "avatar": "https://api.e-phan.com/uploads/users/avatar.jpg",
      "danceClass": 3,
      "country": { "id": 1, "name": "Ukraine", "code": "UA" },

      "status": "INACTIVE",
      "membershipExpiresAt": "2025-12-31",

      "registryState": "REGISTRY",
      "registryUntil": "2026-06-30",
      "archivedAt": null
    }
  ],
  "meta": {
    "page": 1,
    "limit": 20,
    "total": 142,
    "totalPages": 8,
    "hasNextPage": true,
    "hasPreviousPage": false
  }
}

GET /api/v1/clubs

{
  "data": [
    {
      "id": "7c2b9e14-5a83-4d20-91ff-0e6a4b8c1d33",
      "name": "Diamant Elite",
      "logo": "https://api.e-phan.com/uploads/clubs/diamant.png",

      "address": "вул. Хрещатик, 1, Київ (2 поверх)",
      "country": { "id": 1, "name": "Ukraine", "code": "UA" },

      "locations": [
        {
          "type": "main",
          "address": "вул. Хрещатик, 1, Київ (2 поверх)",
          "city": "Київ",
          "country": { "id": 1, "name": "Ukraine", "code": "UA" },
          "placeId": "ChIJBUVa4U7P1EAR_kYBF9IxSXY"
        },
        {
          "type": "branch",
          "address": "вул. М. Гришка, 6А, Київ",
          "city": "Київ",
          "country": { "id": 1, "name": "Ukraine", "code": "UA" },
          "placeId": "ChIJk3Zt1XjP1EARa4LmC2mQhZ0"
        }
      ],

      "dancersCount": 34,
      "coachesCount": 4,
      "registryState": "REGISTRY",
      "registryUntil": null,
      "archivedAt": null
    }
  ],
  "meta": { "page": 1, "limit": 20, "total": 12, "totalPages": 1, "hasNextPage": false }
}

GET /api/v1/clubs/{id}

{
  "data": {
    "id": "7c2b9e14-5a83-4d20-91ff-0e6a4b8c1d33",
    "name": "Diamant Elite",
    "logo": "https://api.e-phan.com/uploads/clubs/diamant.png",

    "address": "вул. Хрещатик, 1, Київ (2 поверх)",
    "country": { "id": 1, "name": "Ukraine", "code": "UA" },
    "locations": [ "…main and every branch…" ],

    "foundedOn": "2003",
    "approvedAt": "2026-04-06T09:44:41.666Z",
    "contacts": {
      "phone": "+380441234567",
      "email": "info@diamant.example",
      "website": "https://diamant.example",
      "instagram": "https://instagram.com/diamant",
      "facebook": null
    },

    "dancersCount": 34,
    "coachesCount": 2,
    "coaches": [
      {
        "id": "b81f0a55-2c19-4f77-9d3e-6a0c5e2b7f41",
        "name": "Ігор", "surname": "Ковальчук",
        "nameEn": "Ihor", "surnameEn": "Kovalchuk",
        "avatar": null,
        "highestClass": 8,
        "country": { "id": 1, "name": "Ukraine", "code": "UA" },
        "registryState": "REGISTRY",
        "registryUntil": null,
        "archivedAt": null
      }
    ],

    "registryState": "REGISTRY",
    "registryUntil": null,
    "archivedAt": null,

    "links": {
      "self": "…",
      "dancers": "…/dancers?clubId=7c2b9e14-…",
      "coaches": "…/coaches?clubId=7c2b9e14-…",
      "web": "…"
    }
  }
}

Кешування

Успішні читання запам’ятовуються в процесі бекенду за ключем «шлях плюс упорядкований рядок запиту», тож повторний виклик не доходить ані до планувальника запитів, ані до серіалізатора. Вам не обов’язково тримати копію календаря, а нашу можна кешувати: відповіді несуть Cache-Control: public, max-age=<TTL>, який CDN або ваш власний проксі може вільно враховувати.

  • Будь-який запис у змагання чи категорію очищає сховище через хуки Sequelize, тож правка стає видимою на наступному запиті, а не після завершення TTL.
  • TTL за ендпоїнтами: 60 с для списку змагань і окремого змагання, 120 с для списків реєстру, 300 с для категорій і значень фільтрів. Запити з updatedSince повністю обходять кеш і відповідають Cache-Control: no-store.
  • X-Cache повідомляє HIT, MISS або BYPASS на кожному читанні. ETag і If-None-Match обробляються і на шляху влучання, і на шляху промаху, тож умовні запити працюють незалежно від стану кешу.
  • entriesCount і startsCount можуть відставати на час TTL: заявки надходять безперервно, і скидання кешу на кожну тримало б його постійно порожнім. Стан реєстру змінюється й за таймером — коли термін просто досягає своєї дати, — тож і там запис може відставати на час TTL.
  • Кешуйте ці відповіді й у себе та продовжуйте віддавати останню вдалу, якщо ми недоступні. Гарантії доступності тут немає — календар, який порожніє через збій, гірший за календар, застарілий на годину.

Умовний запит: 200, потім 304

# 1. Read the data and keep the validator
curl -i "https://api.e-phan.com/api/v1/competitions?period=upcoming"

# HTTP/1.1 200 OK
# ETag: W/"a3f-9Kd1ynQ+Xc0Vv2Bf1lPqE7dZs"
# Cache-Control: public, max-age=60
# X-Cache: MISS

# 2. Ask again, passing the validator back
curl -i -H 'If-None-Match: W/"a3f-9Kd1ynQ+Xc0Vv2Bf1lPqE7dZs"' \
  "https://api.e-phan.com/api/v1/competitions?period=upcoming"

# HTTP/1.1 304 Not Modified
# X-Cache: HIT

Ліміти запитів

300 запитів за хвилину з однієї IP-адреси, у 60-секундному ковзному вікні. Напишіть нам, якщо вашій інтеграції потрібно більше.

  • Кожна відповідь несе X-RateLimit-Limit, X-RateLimit-Remaining і X-RateLimit-Window.
  • Перевищення бюджету дає 429 з Retry-After у секундах і конвертом помилки типу RATE_LIMIT_EXCEEDED.
  • Бюджет витрачає кожен запит, зокрема 304 і влучання в кеш: лічильник спрацьовує до звернення до кешу, тож ліміт обмежує, як часто ви до нас звертаєтесь, а не скільки роботи ми виконуємо.

Помилки

Помилки завжди приходять в однаковому конверті, з машинозчитуваним полем type.

  • 400
    INVALID_REQUEST

    Параметр запиту має непідтримуване значення. Поле details перелічує їх.

  • 404
    NOT_FOUND

    Публічного ресурсу з таким id не існує.

  • 429
    RATE_LIMIT_EXCEEDED

    Хвилинний бюджет вичерпано.

Приклад помилки

{
  "error": {
    "type": "INVALID_REQUEST",
    "message": "One or more query parameters are invalid.",
    "details": ["limit: expected an integer between 1 and 100"]
  }
}

Кожна відповідь несе X-Request-Id. Вкажіть його у зверненні — і ми знайдемо запит у своїх логах. Можете надіслати власний, і ми повернемо його назад, якщо він відповідає [A-Za-z0-9_.:-] і не довший за 64 символи; будь-що інше замінюється згенерованим.

Умови використання

  • Публікуючи ці дані, зазначайте e-phan і давайте посилання на сторінку змагання.
  • API віддає лише опубліковані змагання: чернетки організаторів у стрічку не потрапляють.
  • Нові поля можуть з’являтися без попередження, тож ваш клієнт має ігнорувати незнайомі ключі. Про зміни, що ламають сумісність, повідомляємо заздалегідь і випускаємо їх під новою версією шляху.

Питання щодо інтеграції: support@e-phan.com