{"openapi":"3.1.0","info":{"title":"Treka.eu Partner API","version":"1.0.0","description":"REST API для корпоративних клієнтів Treka.eu: читання фіду пасажирських\nзаявок (лідів), подання власних заявок, керування маршрутами та отримання\nподій через вебхуки.\n\n## Автентифікація\n\nКожен запит потребує **Bearer-токен**, який видає Treka.eu:\n\n```\nAuthorization: Bearer <ваш_токен>\n```\n\nТокен несе **скоупи** (abilities) — ендпоінт відповідає `403`, якщо токену\nбракує потрібного скоупу:\n\n| Скоуп | Призначення |\n|---|---|\n| `leads:read` | Читання лідів |\n| `leads:write` | Створення заявок |\n| `trips:read` | Читання маршрутів |\n| `trips:write` | Керування маршрутами |\n| `webhooks:read` | Читання конфігурації вебхуків |\n\n## Ключі міст\n\n`from_key`, `to_key` та `cities` приймають **канонічний ключ** міста з\nгазетира (напр. `kyiv`, `berlin`, `warsaw`). Ключі не завжди вгадуються —\nчастина латиницею, а частина кирилицею чи поштовим індексом. Не вигадуйте\nїх: знайдіть через `GET /cities?q=Львів` — пошук резолвить довільний текст\nу точний ключ і повертає його першим. (А `POST /messages` ключів взагалі не\nпотребує — він читає місто з тексту сам.)\n\n## Заявки у довільній формі («як у чаті»)\n\nНе хочете збирати структуру самі? Надішліть `POST /messages` із сирим\nтекстом — той самий класифікатор, що читає стрічку спільнот, сам вичитає\nмаршрут, дату, кількість пасажирів і телефон:\n\n```json\n{ \"text\": \"Треба з Києва до Варшави 20 липня, 3 людини, +380631112233\" }\n```\n\nРозпізнавання йде **асинхронно**, тому відповідь одразу — `202 Accepted`\nзі статусом `pending`. Готові ліди приходять **вебхуком** `lead.created`\n(або `lead.matched`, якщо заявка збіглася з вашим маршрутом). Без вебхука\nстатус можна опитати: `GET /messages/{id}` — доки не стане `relevant`\n(є `leads`) або `irrelevant` (заявки в тексті немає).\n\nПотрібен синхронний, повністю контрольований запис? Використайте\nструктурований `POST /leads` замість цього.\n\n## Помилки\n\nБудь-яка помилка повертається єдиним конвертом зі **стабільним кодом**\n`code` — гілкуйтеся по ньому, а не по тексту `message` (він українською і\nможе змінюватися). Поле `errors` присутнє лише для `validation_failed`.\n\n```json\n{ \"code\": \"scope_missing\", \"message\": \"Токену бракує потрібного скоупу.\" }\n```\n\n| `code` | HTTP | Значення |\n|---|---|---|\n| `unauthenticated` | 401 | Токен відсутній або недійсний. |\n| `invalid_token` | 403 | Токен не належить партнерському акаунту. |\n| `access_suspended` | 403 | Партнерський доступ призупинено. |\n| `scope_missing` | 403 | Токену бракує потрібного скоупу (ability). |\n| `plan_restricted` | 403 | Дію не включено у ваш тариф (напр. `scope=all`). |\n| `forbidden` | 403 | Доступ заборонено. |\n| `not_found` | 404 | Ресурс не існує або не належить вам. |\n| `method_not_allowed` | 405 | HTTP-метод не підтримується для цього ресурсу. |\n| `validation_failed` | 422 | Помилка валідації; деталі полів — у `errors`. |\n| `route_limit_reached` | 422 | Досягнуто ліміту активних маршрутів тарифу. |\n| `rate_limited` | 429 | Перевищено ліміт запитів (`rate_limit_per_min`). |\n| `server_error` | 500 | Внутрішня помилка сервера. |\n\n## Ліміти\n\nЗапити обмежені per-компанію (за замовчуванням див. `rate_limit_per_min` у\n`GET /me`). Перевищення → `429 Too Many Requests` з кодом `rate_limited`.\n\n## Скоуп фіду\n\n`GET /leads` за замовчуванням повертає ліди, що збігаються з **вашими\nзареєстрованими маршрутами**. Якщо ваш контракт містить `sees_all_leads`,\nдодайте `?scope=all`, щоб отримати весь фід.\n\n## Перевірка підпису вебхука\n\nКожна подія надходить POST-запитом із заголовками `X-Treka-Event`,\n`X-Treka-Timestamp` та `X-Treka-Signature`. Перевірте підпис перед обробкою:\n\n```\nsignature = \"sha256=\" + hmac_sha256(timestamp + \".\" + raw_body, secret)\n```\n\nPHP:\n\n```php\n$expected = 'sha256=' . hash_hmac(\n    'sha256',\n    $_SERVER['HTTP_X_TREKA_TIMESTAMP'] . '.' . file_get_contents('php://input'),\n    $webhookSecret,\n);\nif (! hash_equals($expected, $_SERVER['HTTP_X_TREKA_SIGNATURE'])) {\n    http_response_code(403);\n    exit;\n}\n```\n\nВідповідайте `2xx`, щоб підтвердити доставку. Невдалі доставки повторюються\nз наростаючою затримкою."},"servers":[{"url":"https://treka.eu/api/v1"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Акаунт","description":"Інформація про токен і компанію"},{"name":"Довідники","description":"Канонічні ключі міст для from_key/to_key/cities"},{"name":"Заявки","description":"Подання заявок у довільній формі («як у чаті»)"},{"name":"Ліди","description":"Читання фіду та подання структурованих заявок"},{"name":"Маршрути","description":"Реєстрація напрямків, за якими матчаться ліди"},{"name":"Вебхуки","description":"Конфігурація та лог доставок подій"}],"paths":{"/me":{"get":{"tags":["Акаунт"],"summary":"Профіль токена та компанії","operationId":"me","responses":{"0":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Account"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/cities":{"get":{"tags":["Довідники"],"summary":"Довідник міст (канонічні ключі)","operationId":"listCities","responses":{"0":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CityCollection"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}},"parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string"},"description":"Пошук за назвою або ключем; також резолвить довільний текст у ключ (напр. `q=Львів`). Точний збіг — першим."},{"name":"country","in":"query","required":false,"schema":{"type":"string"},"description":"Фільтр за ISO-кодом країни (напр. `ua`, `de`, `pl`)."},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer"},"description":"Розмір сторінки (макс. 200)."},{"name":"page","in":"query","required":false,"schema":{"type":"integer"},"description":"Номер сторінки."}]}},"/leads":{"get":{"tags":["Ліди"],"summary":"Список лідів","operationId":"listLeads","responses":{"0":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadCollection"}}}},"1":{"$ref":"#/components/responses/Forbidden"},"401":{"$ref":"#/components/responses/Unauthorized"}},"parameters":[{"name":"scope","in":"query","required":false,"schema":{"type":"string","enum":["mine","all"],"default":"mine"},"description":"`mine` — по своїх маршрутах; `all` — весь фід (потребує sees_all_leads)."},{"name":"direction","in":"query","required":false,"schema":{"type":"string"},"description":"Напрямок, напр. `ua_de`."},{"name":"from","in":"query","required":false,"schema":{"type":"string"},"description":"Канонічний ключ міста відправлення."},{"name":"to","in":"query","required":false,"schema":{"type":"string"},"description":"Канонічний ключ міста призначення."},{"name":"country","in":"query","required":false,"schema":{"type":"string"},"description":"ISO-код країни на будь-якому кінці напрямку."},{"name":"date_from","in":"query","required":false,"schema":{"type":"string"},"description":"Дата поїздки від (Y-m-d)."},{"name":"date_to","in":"query","required":false,"schema":{"type":"string"},"description":"Дата поїздки до (Y-m-d)."},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer"},"description":"Розмір сторінки (макс. 100)."},{"name":"page","in":"query","required":false,"schema":{"type":"integer"},"description":"Номер сторінки."}]},"post":{"tags":["Ліди"],"summary":"Подати заявку","operationId":"createLead","responses":{"0":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadEnvelope"}}}},"1":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadInput"}}}}}},"/leads/{id}":{"get":{"tags":["Ліди"],"summary":"Один лід","operationId":"getLead","responses":{"0":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadEnvelope"}}}},"1":{"$ref":"#/components/responses/NotFound"},"401":{"$ref":"#/components/responses/Unauthorized"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}]}},"/messages":{"post":{"tags":["Заявки"],"summary":"Подати заявку у довільній формі","operationId":"createMessage","responses":{"0":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageEnvelope"}}}},"1":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageInput"}}}}}},"/messages/{id}":{"get":{"tags":["Заявки"],"summary":"Статус і результат розпізнавання","operationId":"getMessage","responses":{"0":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageEnvelope"}}}},"1":{"$ref":"#/components/responses/NotFound"},"401":{"$ref":"#/components/responses/Unauthorized"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}]}},"/trips":{"get":{"tags":["Маршрути"],"summary":"Список маршрутів","operationId":"listTrips","responses":{"0":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripCollection"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"tags":["Маршрути"],"summary":"Зареєструвати маршрут","operationId":"createTrip","responses":{"0":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripEnvelope"}}}},"1":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripInput"}}}}}},"/trips/{trip}":{"get":{"tags":["Маршрути"],"summary":"Один маршрут","operationId":"getTrip","responses":{"0":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripEnvelope"}}}},"1":{"$ref":"#/components/responses/NotFound"},"401":{"$ref":"#/components/responses/Unauthorized"}},"parameters":[{"name":"trip","in":"path","required":true,"schema":{"type":"integer"}}]},"patch":{"tags":["Маршрути"],"summary":"Оновити маршрут","operationId":"updateTrip","responses":{"0":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripEnvelope"}}}},"1":{"$ref":"#/components/responses/NotFound"},"401":{"$ref":"#/components/responses/Unauthorized"}},"parameters":[{"name":"trip","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TripInput"}}}}},"delete":{"tags":["Маршрути"],"summary":"Видалити маршрут","operationId":"deleteTrip","responses":{"0":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"}}}}}},"1":{"$ref":"#/components/responses/NotFound"},"401":{"$ref":"#/components/responses/Unauthorized"}},"parameters":[{"name":"trip","in":"path","required":true,"schema":{"type":"integer"}}]}},"/webhooks":{"get":{"tags":["Вебхуки"],"summary":"Конфігурація вебхуків","operationId":"getWebhooks","responses":{"0":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookConfig"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/webhooks/deliveries":{"get":{"tags":["Вебхуки"],"summary":"Лог доставок","operationId":"listDeliveries","responses":{"0":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}}},"webhooks":{"lead.matched":{"post":{"summary":"Подія `lead.matched`","description":"POST на webhook_url вашої компанії. Підпис у заголовку `X-Treka-Signature`.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"event":{"type":"string","example":"lead.matched"},"occurred_at":{"type":"string","format":"date-time"},"data":{"$ref":"#/components/schemas/Lead"}}}}}},"responses":{"200":{"description":"Підтверджено отримання"}}}},"lead.created":{"post":{"summary":"Подія `lead.created`","description":"POST на webhook_url вашої компанії. Підпис у заголовку `X-Treka-Signature`.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"event":{"type":"string","example":"lead.created"},"occurred_at":{"type":"string","format":"date-time"},"data":{"$ref":"#/components/schemas/Lead"}}}}}},"responses":{"200":{"description":"Підтверджено отримання"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Токен, виданий Treka.eu."}},"responses":{"Unauthorized":{"description":"Токен відсутній або недійсний","content":{"application/json":{"schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Стабільний машинний код помилки — гілкуйтеся по ньому, а не по тексту `message`.","enum":["unauthenticated","invalid_token","access_suspended","scope_missing","plan_restricted","forbidden","not_found","method_not_allowed","validation_failed","route_limit_reached","rate_limited","server_error"],"example":"scope_missing"},"message":{"type":"string","description":"Людиночитний опис (українською, може змінюватися)."},"errors":{"type":"object","description":"Помилки полів — лише для `validation_failed`."}}}}}},"Forbidden":{"description":"Бракує скоупу або доступ призупинено","content":{"application/json":{"schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Стабільний машинний код помилки — гілкуйтеся по ньому, а не по тексту `message`.","enum":["unauthenticated","invalid_token","access_suspended","scope_missing","plan_restricted","forbidden","not_found","method_not_allowed","validation_failed","route_limit_reached","rate_limited","server_error"],"example":"scope_missing"},"message":{"type":"string","description":"Людиночитний опис (українською, може змінюватися)."},"errors":{"type":"object","description":"Помилки полів — лише для `validation_failed`."}}}}}},"NotFound":{"description":"Не знайдено","content":{"application/json":{"schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Стабільний машинний код помилки — гілкуйтеся по ньому, а не по тексту `message`.","enum":["unauthenticated","invalid_token","access_suspended","scope_missing","plan_restricted","forbidden","not_found","method_not_allowed","validation_failed","route_limit_reached","rate_limited","server_error"],"example":"scope_missing"},"message":{"type":"string","description":"Людиночитний опис (українською, може змінюватися)."},"errors":{"type":"object","description":"Помилки полів — лише для `validation_failed`."}}}}}},"ValidationError":{"description":"Помилка валідації","content":{"application/json":{"schema":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Стабільний машинний код помилки — гілкуйтеся по ньому, а не по тексту `message`.","enum":["unauthenticated","invalid_token","access_suspended","scope_missing","plan_restricted","forbidden","not_found","method_not_allowed","validation_failed","route_limit_reached","rate_limited","server_error"],"example":"scope_missing"},"message":{"type":"string","description":"Людиночитний опис (українською, може змінюватися)."},"errors":{"type":"object","description":"Помилки полів — лише для `validation_failed`."}}}}}}},"schemas":{"Account":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"slug":{"type":"string"},"status":{"type":"string"},"sees_all_leads":{"type":"boolean"},"active_route_limit":{"type":"integer","nullable":true},"rate_limit_per_min":{"type":"integer"},"abilities":{"type":"array","items":{"type":"string"}},"webhook":{"type":"object"}}},"City":{"type":"object","properties":{"key":{"type":"string","example":"kyiv","description":"Канонічний ключ — його й ставте у from_key/to_key/cities."},"name":{"type":"string","example":"Київ"},"country":{"type":"object","properties":{"code":{"type":"string","example":"ua"},"name":{"type":"string","example":"Україна"}}}}},"CityCollection":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/City"}},"links":{"type":"object"},"meta":{"type":"object"}}},"Lead":{"type":"object","properties":{"id":{"type":"integer"},"direction":{"type":"string","nullable":true,"example":"ua_de"},"from":{"type":"object","properties":{"city":{"type":"string","nullable":true},"key":{"type":"string","nullable":true},"key_alt":{"type":"string","nullable":true}}},"to":{"type":"object","properties":{"city":{"type":"string","nullable":true},"key":{"type":"string","nullable":true}}},"travel_date":{"type":"string","format":"date","nullable":true},"travel_date_end":{"type":"string","format":"date","nullable":true},"departure_time":{"type":"string","nullable":true},"time_of_day":{"type":"string","nullable":true},"passengers":{"type":"integer","nullable":true},"flags":{"type":"object"},"pet_note":{"type":"string","nullable":true},"comment":{"type":"string","nullable":true},"phone":{"type":"string","nullable":true,"example":"+380631112233"},"viber_link":{"type":"string","nullable":true},"communities_count":{"type":"integer"},"created_at":{"type":"string","format":"date-time"}}},"LeadEnvelope":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Lead"}}},"LeadCollection":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Lead"}},"links":{"type":"object"},"meta":{"type":"object"}}},"Message":{"type":"object","properties":{"id":{"type":"integer"},"status":{"type":"string","enum":["pending","relevant","irrelevant"],"description":"`pending` — ще розпізнається; `relevant` — знайдено заявки (див. `leads`); `irrelevant` — у тексті немає заявки."},"text":{"type":"string"},"author_name":{"type":"string","nullable":true},"leads":{"type":"array","items":{"$ref":"#/components/schemas/Lead"}},"created_at":{"type":"string","format":"date-time"}}},"MessageEnvelope":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Message"}}},"MessageInput":{"type":"object","required":["text"],"properties":{"text":{"type":"string","minLength":2,"maxLength":2000,"example":"Треба з Києва до Варшави 20 липня, 3 людини, +380631112233"},"author_name":{"type":"string","nullable":true,"maxLength":120,"example":"Олена"}}},"LeadInput":{"type":"object","required":["from_key","to_key","travel_date","passengers","phone"],"properties":{"from_key":{"type":"string","example":"kyiv"},"to_key":{"type":"string","example":"berlin"},"travel_date":{"type":"string","format":"date","example":"2026-07-20"},"travel_date_end":{"type":"string","format":"date","nullable":true},"passengers":{"type":"integer","minimum":1,"maximum":20,"example":2},"phone":{"type":"string","example":"+380631112233"},"departure_time":{"type":"string","example":"08:30","nullable":true},"time_of_day":{"type":"string","enum":["morning","day","evening","night"],"nullable":true},"has_airport":{"type":"boolean"},"has_children":{"type":"boolean"},"has_recliner":{"type":"boolean"},"has_layout_223":{"type":"boolean"},"has_pet":{"type":"boolean"},"is_urgent":{"type":"boolean"},"pet_note":{"type":"string","nullable":true},"comment":{"type":"string","nullable":true}}},"Trip":{"type":"object","properties":{"id":{"type":"integer"},"status":{"type":"string","example":"active"},"direction":{"type":"string","nullable":true},"origin_city":{"type":"string","nullable":true},"destination_city":{"type":"string","nullable":true},"cities":{"type":"array","items":{"type":"string"}},"city_keys":{"type":"array","items":{"type":"string"}},"pickup_radius_km":{"type":"integer"},"dropoff_radius_km":{"type":"integer"},"date_from":{"type":"string","format":"date","nullable":true},"date_to":{"type":"string","format":"date","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"TripEnvelope":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Trip"}}},"TripCollection":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Trip"}},"links":{"type":"object"},"meta":{"type":"object"}}},"TripInput":{"type":"object","required":["cities"],"properties":{"cities":{"type":"array","items":{"type":"string"},"minItems":2,"example":["kyiv","berlin"]},"pickup_radius_km":{"type":"integer","enum":[50,100,200,300]},"dropoff_radius_km":{"type":"integer","enum":[50,100,200,300]},"date_from":{"type":"string","format":"date","nullable":true},"date_to":{"type":"string","format":"date","nullable":true},"status":{"type":"string","enum":["active","archived"]}}},"WebhookConfig":{"type":"object","properties":{"configured":{"type":"boolean"},"url":{"type":"string","nullable":true},"secret_set":{"type":"boolean"},"events":{"type":"array","items":{"type":"string"}},"signature":{"type":"object"}}},"Error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Стабільний машинний код помилки — гілкуйтеся по ньому, а не по тексту `message`.","enum":["unauthenticated","invalid_token","access_suspended","scope_missing","plan_restricted","forbidden","not_found","method_not_allowed","validation_failed","route_limit_reached","rate_limited","server_error"],"example":"scope_missing"},"message":{"type":"string","description":"Людиночитний опис (українською, може змінюватися)."},"errors":{"type":"object","description":"Помилки полів — лише для `validation_failed`."}}}}}}