1. Quick start
Authentication шаардлагагүйAPI key заавал биш Бүх endpoint нээлттэй, HTTPS. Base URL:
https://api.bulgan-trip.mn/api/v1
Гурван request-ээр бүх зүйл гарна:
# 1. Аймгийн id авна (одоогоор зөвхөн Булган)
curl -s "https://api.bulgan-trip.mn/api/v1/provinces"
# 2. Тэр id-гаар бүх өгөгдлийг нэг дор татна
curl -s "https://api.bulgan-trip.mn/api/v1/sync/snapshot?province=<province_id>&locale=mn"
# 3. Дараа нь өөрчлөгдсөн эсэхийг л шалгана (хөнгөн)
curl -s "https://api.bulgan-trip.mn/api/v1/sync/manifest?province=<province_id>"
2. Endpoints
Province — GET /provinces
[
{
"id": "4e3af807-59c2-40dc-8bcc-7d613f2bacdd",
"code": "bulgan",
"name_mn": "Булган",
"name_en": "Bulgan",
"status": "active"
}
]
Одоогоор зөвхөн Булган идэвхтэй. Энэ id-г доорх request-үүдэд хэрэглэнэ.
Snapshot — GET /sync/snapshot?province=<id>&locale=mn
Жагсаалт барихад энэ нэг request хангалттай.
Ойролцоогоор 500 КБ. Бүх идэвхтэй place, category, district, route, event-ийг агуулна. Хариу:
{
"synced_at": "2026-09-29T11:57:38Z",
"province_id": "4e3af807-…",
"categories": [ … 15 ],
"districts": [ … 16 ],
"places": [ … 163 ],
"routes": [ … ],
"events": [ … ]
}
places дотор зөвхөн нийтлэгдсэн, идэвхтэй газрууд ирнэ. Нуусан эсвэл устгасан
газар огт орохгүй тул тусад нь шүүх шаардлагагүй.
Manifest — GET /sync/manifest?province=<id>
{
"province_id": "4e3af807-…",
"updated_at": "2026-09-29T09:14:45Z",
"counts": { "places": 163, "categories": 15, "routes": 2, "events": 3 }
}
Хэдхэн байт. updated_at өөрчлөгдсөн үед л snapshot-оо дахин татна.
Өдөрт нэг удаа poll хийхэд хангалттай.
Filter, search — GET /places
Server талаас filter, search, pagination хийх шаардлагатай бол:
GET /places?locale=mn&page=1&page_size=50
GET /places?locale=mn&q=рашаан
GET /places?locale=mn&category=<category_id>
GET /places?locale=mn&lat=48.8125&lng=103.5347 # зайгаар эрэмбэлнэ
GET /places?locale=mn&lat=…&lng=…&radius_km=10 # ойролцоох
Response нь { "items": [...], "page": 1, "page_size": 50 }.
lat/lng өгвөл газар бүрт distance_m нэмэгдэнэ — энэ нь
замаар явах бодит зай (шулуун шугамын зай биш), метрээр.
page parameter-ийг үргэлж тодорхой бичиж өгнө үү.
3. Place object
Хамгийн их хэрэглэгддэг field-үүд:
| Field | Тайлбар |
|---|---|
id | UUID. Хэзээ ч өөрчлөгдөхгүй. Өөрийн талдаа энэ id-гаар холбоно. |
deeplink | Аппыг нээх URL. Доорх 4-р хэсгийг үзнэ үү. |
translations | mn, en, ko, zh, ru key-тэй. Тус бүрд name, short_desc, description. |
location | { "lat": 49.169, "lng": 102.351 } |
cover_image_url | Үндсэн зургийн бүтэн URL. Байхгүй бол null. |
images | Бүх зураг: { id, url, is_cover, sort_order } |
category_id | Snapshot-ийн categories-той тааруулна. |
district_id | Сум. districts-тай тааруулна. |
contacts | [{ "type": "phone", "value": "95826063" }] — дугаар бүр тусдаа мөр. |
price_status | free | paid | unknown |
price_min, price_max | Үнийн хязгаар. price_currency ихэвчлэн MNT. Байхгүй бол null. |
is_24h, open_time, close_time | Ажиллах цаг. Цаг нь "09:00" хэлбэртэй, Улаанбаатарын цагаар. |
slug | Cache хийж хадгалж болохгүй. Админаас гараар засагддаг тул хожим өөрчлөгдөж болно. Identifier болгож зөвхөн id хэрэглэнэ. |
4. Deeplink
Place бүрийн deeplink field-ийг хэвээр нь хэрэглэнэ. Өөрөө URL угсрах шаардлагагүй.
"deeplink": "https://bulgan-trip.mn/place/4a569305-c799-4529-9d9d-534e6c9e44d5"
Энэ бол энгийн HTTPS URL. Хэрэглэгч дээр нь дарахад:
- Bulgan Trip апп суулгаатай бол апп шууд нээгдэж тухайн газрын хуудас гарна;
- суулгаагүй бол ижил URL вэб дээр тэр газрыг харуулна.
Сонгох цонх гарахгүй, «апп олдсонгүй» алдаа гарахгүй. Та апп суусан эсэхийг шалгах шаардлагагүй — үүнийг үйлдлийн систем өөрөө хийнэ.
Код жишээ
// Android (Kotlin)
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(place.deeplink)))
// iOS (Swift)
if let url = URL(string: place.deeplink) {
UIApplication.shared.open(url)
}
// Flutter
import 'package:url_launcher/url_launcher.dart';
await launchUrl(Uri.parse(place.deeplink), mode: LaunchMode.externalApplication);
<!-- Вэб -->
<a href="https://bulgan-trip.mn/place/4a569305-…">Дэлгэрэнгүй</a>
Анхаарах: deeplink-ийг WebView дотор биш,
external байдлаар нээнэ үү (Flutter дээр LaunchMode.externalApplication).
WebView дотор нээвэл апп руу шилжихгүй, зүгээр вэб хуудас болж харагдана.
Мөн bulgan-trip:// гэсэн схем байдаг ч
түүнийг хэрэглэхгүй: апп суулгаагүй үед чимээгүй бүтэлгүйтдэг тул fallback байхгүй.
5. Locale, зураг
Locale. Snapshot-д 5 хэлний орчуулга бүгд ирдэг тул хэл солиход дахин татах
шаардлагагүй — translations дотроос сонгоод харуулна. Тухайн locale-д орчуулга
байхгүй бол mn-г fallback болгож хэрэглэнэ.
Зураг. Бүх зураг https://cdn.bulgan-trip.mn/… дээр байрлана, шууд
ачаалж болно. Resize хийх query parameter байхгүй тул өөрийн талдаа cache хийж, багасгахыг
зөвлөе. Зураггүй газар байж болох тул cover_image_url нь null
байх тохиолдлыг заавал бодолцоорой.
6. Rate limit, API key
| Дүрэм | Яагаад |
|---|---|
GET /places/:id-г poll хийж бүү дууд | Request бүр тухайн газрын view count-ыг нэмдэг тул манай статистик гуйвна. Жагсаалтаа snapshot-оос ав. |
| Өгөгдлийг өөртөө cache хий | Content өдөр бүр өөрчлөгддөггүй. manifest-ээр шалгаад шаардлагатай үед л snapshot-оо дахин тат. |
Identifier болгож id хэрэглэ | slug өөрчлөгдөж болно, id өөрчлөгдөхгүй. |
deeplink-ийг хэвээр нь хадгал | Форматыг бид цаашид өөрчилж болно. Field-ийг нь хэрэглэвэл танд өөрчлөлт хэрэггүй. |
| Attribution | Мэдээлэл Bulgan Trip-ээс гаралтай гэдгийг хэрэглэгчдэд ойлгомжтой байлгана уу. |
Rate limit. IP хаяг тус бүрээр:
| Endpoint | Rate limit |
|---|---|
/sync/snapshot, /routing/* | минутад 30 |
| бусад бүх нээлттэй endpoint | минутад 240 |
Хэвийн ашиглалтад хүрэхгүй тоо. Хэтэрвэл 429 Too Many Requests буцаах бөгөөд
Retry-After header-т хэдэн секунд хүлээхийг бичнэ. Snapshot-оо нэг удаа татаад
cache хийвэл энэ limit-д огт ойртохгүй.
API key (санал болгоё)
Бидэнтэй холбогдож API key авч болно. API key-тэй бол:
| Endpoint | API key-гүй | API key-тэй |
|---|---|---|
/sync/snapshot, /routing/* | минутад 30 | минутад 120 |
| бусад | минутад 240 | минутад 600 |
Request бүрдээ ийн бичнэ:
X-API-Key: bt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
API key-гүй ч ажиллана. Энэ нь authentication биш — зөвхөн таныг таних, илүү өндөр rate limit өгөх зориулалттай. Буруу эсвэл хүчингүй болсон key илгээсэн ч request татгалзагдахгүй, зүгээр key-гүйтэй адил limit-тэй болно.
Key-г нууцлах шаардлагагүй ч бусадтай хуваалцахгүй байхыг хүсье — асуудал гарвал бид тухайн key-г л идэвхгүй болгоно.
Нэмэлт. Request бүрдээ X-Client header-т аппынхаа нэр, хувилбарыг
бичиж өгвөл баярлалаа (жишээ нь X-Client: tavan-bogd-app/2.1).
CORS. Browser-оос шууд fetch хийх бол таны origin-ийг манай allowlist-д
нэмэх шаардлагатай — бидэнд domain-аа илгээнэ үү. Native app эсвэл server-side request-д
CORS хамаарахгүй.
7. Холбоо барих
Асуулт, CORS-д domain нэмүүлэх, bug мэдээлэх: info@bulgan-trip.mn
Апп: bulgan-trip.mn · Ашиглалтын заавар