Клиентска документация: ERA Publisher – Универсален формат и интеграция

Този документ описва как външни медии (портали за недвижими имоти) интегрират своята система с ERA Publisher.
Акцентът е върху формата на файла output.xml, процеса на синхронизация и обратната връзка за статус.

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

  1. Медията създава акаунт за всеки офис на ЕРА, който ще ползва услугата. Всеки акаунт се удостоверява с token, който медията предоставя на ЕРА при създаването на офис акаунта. Този token е и паролата на акаунта на офиса.
  2. Към този акаунт трябва да има линк, от който медията чете текущия статус на офертите за дадения офис. Линкът се предоставя от ЕРА. Формат: https://publisher.era.bg/media/{media_name}/{office_name}
  3. Медията предоставя на ЕРА route за нотификация, към който ЕРА прави POST заявка за да уведоми медията, че трябва да започне импорт/синхронизация на офертите за съответния офис, като подава token, удостоверяващ офиса. Този token е предоставен в т. 1. и ще бъде получен в хедърите на заявката, като API-KEY:
                  'Content-Type' => 'application/json',
                  'API-KEY' => {token}
  4. Медията предоставя на ЕРА максимален брой изображения (image_limit) за една оферта. Системата на ЕРА ще конфигурира лимита според стойността, предоставена от медията, и ще отрязва излишните изображения до този лимит при генериране на output.xml.

Процедура по синхронизация

  1. ЕРА изпраща нотификация към медията чрез предоставения от медията route, че информацията за конкретен офис е обновена.
  2. Медията прочита файла на офиса: https://publisher.era.bg/media/{media_name}/{office_name} и синхронизира информацията.
  3. Медията изпраща статус на операцията на линка, посочен в полето <result_url> на output.xml.

Точка за достъп до output.xml

  • Публичен URL по шаблон: https://publisher.era.bg/media/{media_name}/{office_name}
  • Файлът се генерира от системата и се обновява при налични промени за офиса.
  • При грешка в предходен импорт системата пази маркер (started.lock) и може да отложи следващото стартиране до приключване.

Формат на output.xml

  • Кодиране: UTF-8
  • XML декларация: <?xml version="1.0" encoding="UTF-8"?>
  • Коренов елемент: feed
  • Структура (в този ред):
    1. updated – дата и час на генериране (формат: YYYY-MM-DD hh:mm:ss, локално време).
    2. agency_uid – уникален идентификатор на офиса. В момента се попълва с телефонния номер на офиса.
    3. agency_name – име на офиса.
    4. agency_phone – телефон на офиса.
    5. agency_email – имейл на офиса.
    6. result_url – абсолютен URL (HTTP/HTTPS), на който медията трябва да изпрати резултата от импорта/синхронизацията за този офис.
    7. Brokers – масив с нула или повече брокери, които са активни в момента.
    8. offers – масив с нула или повече оферти, които са активни в момента. Ако от масива с оферти липсват оферти, които са текущо публикувани, то системата автоматично ще ги направи непубликувани/архивирани. Този масив с оферти трябва да съдържа само офертите, които искате да са публикувани.
Пример:
XML
<?xml version="1.0" encoding="UTF-8"?>
<feed>
  <updated>2024-10-07 11:47:22</updated>
  <agency_uid>+359896523887</agency_uid>
  <agency_name>Решение</agency_name>
  <agency_phone>+359896523887</agency_phone>
  <agency_email>reshenie@era.bg</agency_email>
  <result_url>https://publisher.era.bg/media/{media}/callback/{officeId}</result_url>
  <Brokers>
    <Broker>
      <BrokerName>Румяна Илиева</BrokerName>
      <BrokerID>22329</BrokerID>
      <BrokerPhone>0897975071</BrokerPhone>
      <BrokerEmail>rilieva@era.bg</BrokerEmail>
    </Broker>
  </Brokers>
  <offers>
    <offer>
      <last_update>2024-10-07</last_update>
      <offer_uid>155174</offer_uid>
      <description>…</description>
      <images>http://…/p1.jpg,http://…/p2.jpg</images>
      <price>36500</price>
      <surface>1098</surface>
      <currency>eur</currency>
      <category>продажби</category>
      <subcategory>ПАРЦЕЛ</subcategory>
      <internal_id>15975</internal_id>
      <building_condition></building_condition>
      <furnished></furnished>
      <heating_ids></heating_ids>
      <rooms_count></rooms_count>
      <bedroom_count>0</bedroom_count>
      <broker_id>0896523887</broker_id>
      <offer_code>5700097</offer_code>
      <surface_parcel>1098</surface_parcel>
      <floor>0</floor>
      <parking_ids></parking_ids>
      <balcony_count>0</balcony_count>
      <bath_count>0</bath_count>
      <Exclusive>1</Exclusive>
    </offer>
  </offers>
</feed>

Полета в feed

  • updated (string) – време на генериране. Формат: YYYY-MM-DD hh:mm:ss.
  • agency_uid (string) – уникален идентификатор на офиса. В текущата имплементация е телефонен номер на офиса.
  • agency_name (string) – име на офиса.
  • agency_phone (string) – телефон на офиса (E.164 препоръчителен формат).
  • agency_email (string) – валиден имейл адрес.
  • result_url (URL) – абсолютен URL за обратна връзка. Динамично се генерира и включва идентификатор на офиса и името на медията.

Секция Brokers

  • Масив за 0..N брокера. Когато няма брокери, елементът все пак присъства, но може да е празен.
  • Елемент Broker съдържа:
    • BrokerName (string)
    • BrokerID (string|int)
    • BrokerPhone (string)
    • BrokerEmail (string)
  • Забележка: Секцията се композира от валидни XML фрагменти; невалидни фрагменти се пропускат.

Секция offers

  • Масив за 0..N оферти. Когато няма оферти, елементът присъства, но може да е празен.
  • Елемент offer включва често срещани полета:
    • last_update (datetime) – YYYY-MM-DD hh:mm:ss
    • offer_uid (int)
    • description (string)
    • images (string) – абсолютни URL-и, разделени с ; (до зададения image_limit)
    • price (number), surface (number), currency (enum), category (enum), subcategory (enum)
    • internal_id (int)
    • и др.: building_condition, furnished, heating_ids, rooms_count, bedroom_count, broker_id, offer_code, surface_parcel, floor, parking_ids, balcony_count, bath_count, Exclusive
  • Невалидни XML фрагменти за единични оферти се пропускат.

Допустими стойности (offers)

  • last_update: формат YYYY-MM-DD hh:mm:ss.
  • offer_uid: уникален и стабилен във времето.
  • offer_code: референтен код.
  • description: текст.
  • images: множество абсолютни URL адреси, разделени с ;.
  • price, surface, surface_parcel: цели числа.
  • currency (enum): едно от [eur, лв.]; от 01.01.2026 – само eur.
  • category (enum): [продажби, наеми].
  • subcategory (enum): „СТАЯ“, „1-СТАЕН“, „2-СТАЕН“, „3-СТАЕН“, „4-СТАЕН“, „МНОГОСТАЕН“, „МЕЗОНЕТ“, „ОФИС“, „АТЕЛИЕ“, „АТЕЛИЕ, ТАВАН“, „ТАВАН“, „ГАРСОНИЕРА ЛУКС“, „ГАРСОНИЕРА“, „ЕТАЖ ОТ КЪЩА“, „КЪЩА“, „ВИЛА“, „МАГАЗИН“, „ЗАВЕДЕНИЕ“, „СКЛАД“, „ГАРАЖ“, „ПРОМ. ПОМЕЩЕНИЕ“, „ХОТЕЛ“, „ПАРЦЕЛ“, „ЗЕМЕДЕЛСКА ЗЕМЯ“, „СГРАДИ“, „ЗАТВОРЕН КОМПЛЕКС“.
  • internal_id: стойност от колоната internal_id на geo_db.csv.
  • construction_type (enum): [епк, панел, тухла, гредоред, метална конструкция].
  • floor, floorAll, rooms_count, bedroom_count, balcony_count, bath_count: цели числа.
  • building_condition (enum): [Completed, UnderConstruction, PreConstruction, SemiCompleted, NotCompleted].
  • build_year: дата YYYY-MM-DD.
  • furnished (enum): една стойност от [14 (С обзавеждане), 23 (Без обзавеждане), 65 (Частично обзавеждане)].
  • heating_ids: една или повече стойности от [18 (Камина), 35 (Климатик), 44 (Ток), 46 (Локално парно), 80 (Отопление)], разделени със запетая.
  • parking_ids: една или повече стойности от [52 (С гараж), 93 (С паркинг)], разделени със запетая.
  • exterior_ids: една или повече стойности от [10 (Асфалтиран), 42 (Има асансьор)], разделени със запетая.
  • new_construction (enum): 0 или 1.
  • land_category (enum): 0..10.
  • parcel_in_regulation (enum): True или False.
  • broker_id: номер – трябва да съвпада с BrokerID от Brokers.
  • youTube_link, virtual_panorama_link: URL.
  • Exclusive (enum): 0 или 1.

Ограничения и технически бележки

  • Кодиране: UTF-8.
  • Валидност на XML: Системата валидира, че output.xml е добре формиран; при грешка файлът не се публикува.
  • Максимален брой изображения: определя се от медията; системата отрязва излишните до зададения лимит.
  • Часови прозорци за пускане: изпълнение само в разрешен часови диапазон (конфигурируем).

Callback към ЕРА: result_url

  • Полето result_url указва крайна точка, на която медията изпраща резултат от процеса на импорт.
  • Адресът е абсолютен и съдържа идентификатор на офиса и името на медията.
  • HTTP метод: POST
  • Content-Type: application/json
  • Формат на тялото: JSON, валидиран от UniversalProcessPublishedStatus:
    • logResult: масив от низове (индекс 0 – обобщение) – required
    • logOffers: масив от обекти – required; всеки обект включва:
      • errors: масив от низове (може да е празен)
      • offerSourceData: обект с offer_uid (низ, required)
JSON
{
  "logResult": ["Import completed with 3 offers processed."],
  "logOffers": [
    {
      "errors": [],
      "offerSourceData": {"offer_uid": "155174"}
    },
    {
      "errors": ["Missing price", "Invalid currency"],
      "offerSourceData": {"offer_uid": "155175"}
    }
  ]
}

Грешки при импорт в медията

Медията връща статус на result_url (POST, JSON). Препоръчителни HTTP кодове:

  • 200 OK – импортът е изпълнен; в logResult[0] има обобщение, а в logOffers – детайли по офертите.
  • 400 Bad Request – JSON невалиден/липсващо поле.
  • 401/403 – неуспешна автентикация/авторизация (невалиден токен/подпис).
  • 409 Conflict – конфликтно състояние (напр. дублиран процес).
  • 413 Payload Too Large – надхвърлен допустим размер.
  • 415 Unsupported Media Type – грешен Content-Type (изисква се application/json).
  • 422 Unprocessable Entity – семантични грешки (невалидни стойности по полета); включете подробности в errors.
  • 429 Too Many Requests – ограничение на скоростта.
  • 500/503 – временна вътрешна/инфраструктурна грешка; опитайте отново с backoff.

Препоръки за устойчивост

  • Частичен провал: отбелязвайте конкретните оферти с грешки в logOffers[*].errors, но връщайте 200 ако процесът глобално е успешен.
  • Ограничаване на изображенията до image_limit; излишните се игнорират.

Сигурност

  • Използвайте само HTTPS за всички крайни точки.
  • Идентификация на офис/медия чрез токен; пазете го поверително и го подменяйте при инциденти.
  • Препоръчително: подписване на заявки (напр. HMAC-SHA256 над тялото + timestamp) и валидиране в двете посоки.
  • Ограничете достъпа до result_url чрез IP allowlist и/или rate limiting.
  • Валидация на входа: Content-Type: application/json, максимални размери на тяло и полета.
  • Политика за съхранение: пазете логове за одит за разумен период, след което ги анонимизирайте/изтрийте според политиките ви.

Версиониране и съвместимост

  • Стабилни идентификатори: offer_uid, BrokerID, internal_id.
  • Депрeкации: валутата лв. се премахва на 01.01.2026 – приемайте само eur след тази дата.
  • Добавяне на нови полета се прави по backward-compatible модел (неизвестните полета се игнорират).

Често задавани въпроси

  • Какво става при липсващи изображения?images може да е празно; импортът не трябва да се проваля.
  • Можем ли да извикаме импорт без нотификация? – Да, при наличие на нов output.xml, но спазвайте rate limiting.

Контакти и поддръжка

  • Оперативни въпроси за интеграция: вашият контакт в ЕРА.
  • Инциденти в продукция: уведомете поддръжката и предоставете timestamp, office_name, media_name, примерни offer_uid.

История на промените

  • 1.0 (2025-09-02): Първоначална версия на документацията, включваща формат на фийда, синхронизация и callback.
  • 1.1 (2025-09-12): Промяна на линка на точка за достъп до output.xml от https://publisher.era.bg/{media_name}/{office_name}/output.xml на https://publisher.era.bg/media/{media_name}/{office_name}
  • 1.2 (2025-10-22): Поясняване на употребата на ендпойнта за стартиране на импорт