Places
Seven endpoints return places and what is known about them. Together they are billed as the places product.
| Endpoint | Returns |
|---|---|
GET /api/nearby | Places around a point |
GET /api/place/{id} | One place with contact details, opening hours and neighbours |
GET /api/place/osm/{n|w|r}/{id} | The same, addressed by OpenStreetMap id |
GET /api/geometry/{id} | A place's outline as GeoJSON |
GET /api/categories | The category taxonomy |
GET /api/districts | Region, district and mahalla label points as GeoJSON |
GET /api/photo | Photo metadata for a Wikidata id |
GET /api/photo/img/{qid}.jpg | The photo itself |
All handlers live in services/server/internal/server/server.go. Examples below use $UZMAPS for the server origin (for instance http://127.0.0.1:8080 for a local uzmap serve), and the JSON shown is real output from a current build, trimmed where marked ….
What applies to every endpoint#
API key. When the server runs with --require-key, every /api/ path needs one. Pass it as ?key=… or as an X-API-Key header; the query parameter is checked first. Photo image URLs are the case where the query form is the only option — see Photos. A missing or unknown key gets a 401 JSON body with error, code, message and docs. The key mechanism is described on the API keys page.
lang. Accepted values are uz (default), uz-Cyrl, ru and en. Anything else silently becomes uz (lang() in server.go). It affects name, label, category_label and full_address.
Errors are JSON {"error": "…"}. Every place endpoint returns 503 with search index not built (districts: index not built) when data/build/search.sqlite is missing. The photo image endpoint is the exception: its 404 is plain text, from http.NotFound.
CORS. Access-Control-Allow-Origin: * on every response (withCommon).
The place object#
/api/nearby returns a list of these; /api/place/{id} returns one with extra fields added. The shape is resultJSON in server.go.
| Field | Type | Notes |
|---|---|---|
id | integer | Stable within one data build. This is the id /api/place/{id} and /api/geometry/{id} take. |
kind | string | place, street, poi, address or nature. |
category | string | Taxonomy id such as food.cafe. See Categories. |
group | string | The category's group, e.g. food. Derived by taxonomy.GroupOf. |
icon | string | Sprite icon name. The category's icon, falling back to the group's, then poi (taxonomy.IconFor). |
name | string | Best name for lang (resolution below). |
label | string | Secondary line: Park · Sharof Rashidov, Toshkent. |
category_label | string | Category label in lang. |
lat, lon | number | Representative point. |
bbox | [minlon, minlat, maxlon, maxlat] | Equals [lon, lat, lon, lat] for point features. |
distance | number | Metres from the query point. Omitted when not computed and, because zero counts as empty, when it is exactly 0. |
address | object | See below. Always present, possibly {}. |
names | object | Name variants keyed by language or OSM name key. Omitted when there are none. |
rank | number | Importance, 0–1. Used for ordering. |
matched, reason | Search-only fields; absent from place endpoints. |
Name resolution: uz → names.uz; uz-Cyrl → names["uz-Cyrl"], then names.ru; ru → names.ru, then names["uz-Cyrl"]; en → names.en, then names.uz. If nothing matches, the primary name is used, and if the feature has no name at all, the category label stands in for it.
names keys come from pickNames in internal/osmx/extract.go: the name: prefix is stripped, so OSM's name:uz-Cyrl becomes uz-Cyrl, while un-prefixed keys stay as they are. Possible keys: uz, uz-Latn, uz-Cyrl, ru, en, kaa, tg, kk, alt_name, old_name, official_name, int_name, short_name, etymology. Overture places carry only uz, uz-Cyrl, ru, en.
Category labels for uz-Cyrl come out in English. taxonomy.Label falls back lang → en → uz, and the taxonomy only defines uz, ru and en labels.
address (store.Address): street, housenumber, neighbourhood, suburb, city, district, region, postcode — strings, each omitted when empty.
GET /api/nearby#
Places within a radius of a point.
| Parameter | Default | Notes |
|---|---|---|
lat, lon | 0 | Not validated. Omitting them searches around 0°,0° and returns {"results":[]}. |
radius | 500 | Metres. Values above 20000 are clamped, not rejected. |
kinds | poi | Comma-separated list of kinds. |
category | — | Exact category id (food.cafe) or a whole group (group:food). Nothing looser: food.cafe does not include food.teahouse. |
limit | 20 | 0 or negative means no limit. |
lang | uz |
Results are ordered by rank × 0.4 + 0.6 / (1 + distance / 300) (Index.Nearby), so a prominent place 100 m away can come before an obscure one 20 m away. Before the limit is applied, entries with the same normalised name, kind and city within a kind-dependent radius — 250 m for POIs — are collapsed to one, which is why a bazaar mapped both as a building and as a point appears once.
curl "$UZMAPS/api/nearby?lat=41.3111&lon=69.2797&radius=300&limit=3&lang=en"{"results":[
{"id":112943,"kind":"poi","category":"leisure.park","group":"leisure","icon":"park",
"name":"Square","label":"Park · Sharof Rashidov, Ташкент","category_label":"Park",
"lat":41.31128571278156,"lon":69.27975493574449,
"bbox":[69.2779275,41.3100336,69.2813994,41.312818],"distance":21.153959974222012,
"address":{"neighbourhood":"Sharof Rashidov","city":"Ташкент","district":"Yunusobod Tumani"},
"names":{"en":"Square","ru":"Сквер","uz":"Skver","alt_name":"…","old_name":"…"},
"rank":0.8500000000000001},
{"id":455,"kind":"poi","category":"culture.monument","group":"culture","icon":"monument",
"name":"Amir Timur","label":"Monument · Sharof Rashidov, Toshkent","category_label":"Monument",
"lat":41.3111759,"lon":69.2797551,"bbox":[69.2797551,41.3111759,69.2797551,41.3111759],
"distance":9.612904450845452,
"address":{"neighbourhood":"Sharof Rashidov","city":"Toshkent","district":"Yunusobod Tumani"},
"names":{"en":"Amir Timur","ru":"Амир Тимур","uz":"Amir Temur"},"rank":0.75},
…
]}GET /api/place/{id}#
One place, with everything the index holds about it. {id} is the integer from a result object. The alternative form /api/place/osm/{type}/{id} looks the entry up by its OpenStreetMap type letter and id instead; /api/place/osm/n/377979982 returns the monument above.
| Status | Body | When |
|---|---|---|
400 | bad id | {id} is not an integer |
400 | use /api/place/osm/{n|w|r}/{id} | The osm/ form has the wrong number of segments |
404 | not found | No entry with that id |
The response is the place object plus:
| Field | Type | Notes |
|---|---|---|
osm | object | {type, id, url} — see Where a place came from. |
tags | object | Source tags, filtered to a fixed allowlist (below). |
population | integer | 0 when unknown. |
admin_level | integer | 0 when unknown. |
full_address | string | street[, housenumber] or neighbourhood; then district (if different from city); then city, or region if there is no city. With nothing to show it is Xaritadagi nuqta / Точка на карте / Dropped pin by lang. |
details | object | Contact and attribute fields lifted out of tags — table below. Always present, possibly {}. |
photo_hint | string | /api/photo?wikidata=Q…, present only when photos are enabled and the entry has a wikidata tag. The path carries no key; add yours. |
nearby | array or null | Up to six named POIs within 350 m, excluding the place itself. null — not [] — when there are none. |
geometry_url | string | /api/geometry/{id}, present when the entry has stored geometry and its kind is street, place, nature or poi. |
details keys, in the order the handler builds them:
| Key | Taken from |
|---|---|
phone | phone, else contact:phone |
website | website, else contact:website |
instagram | contact:instagram |
telegram | contact:telegram |
email | email, else contact:email |
cuisine, wheelchair, internet_access, outdoor_seating, takeaway, delivery, building:levels, height, capacity, fee, stars, ele, iata, brand, operator, description, sport, denomination, wikipedia, wikidata | The tag of the same name, copied verbatim when present |
opening_hours | The raw opening_hours tag |
hours, open_status | Parsed from opening_hours — see Opening hours |
tags is not the full OSM tag set. The extractor keeps only these keys: opening_hours, phone, contact:phone, website, contact:website, contact:instagram, contact:telegram, email, contact:email, brand, operator, cuisine, wheelchair, wikidata, wikipedia, description, level, building:levels, height, addr:street, addr:housenumber, addr:city, addr:district, addr:place, addr:quarter, addr:suburb, addr:postcode, addr:neighbourhood, addr:block, amenity, shop, tourism, leisure, office, historic, natural, place, highway, railway, aeroway, public_transport, religion, denomination, sport, internet_access, outdoor_seating, takeaway, delivery, smoking, capacity, fee, parking, fuel:cng, fuel:lpg, fuel:diesel, fuel:octane_95, stars, rooms, population, admin_level, ele, iata, icao, network, ref, route_ref, colour, image, school:language, healthcare, dispensing, drive_through, payment:cards.
curl "$UZMAPS/api/place/112943?lang=en"{"id":112943,"kind":"poi","category":"leisure.park","group":"leisure","icon":"park",
"name":"Square","label":"Park · Sharof Rashidov, Ташкент","category_label":"Park",
"lat":41.31128571278156,"lon":69.27975493574449,
"bbox":[69.2779275,41.3100336,69.2813994,41.312818],
"address":{"neighbourhood":"Sharof Rashidov","city":"Ташкент","district":"Yunusobod Tumani"},
"names":{"en":"Square","ru":"Сквер","uz":"Skver","alt_name":"…","old_name":"…"},
"rank":0.8500000000000001,
"osm":{"type":"w","id":802384567,"url":"https://www.openstreetmap.org/way/802384567"},
"tags":{"leisure":"park","natural":"grass","opening_hours":"24/7","ele":"455",
"website":"https://mytashkent.uz/2007/06/05/skver-amira-temura/",
"wikidata":"Q4421686","wikipedia":"ru:Сквер Эмира Тимура",
"addr:city":"Ташкент","addr:street":"Сквер Амира Тимура"},
"population":0,"admin_level":0,
"full_address":"Sharof Rashidov, Yunusobod Tumani, Ташкент",
"details":{"ele":"455","website":"https://mytashkent.uz/2007/06/05/skver-amira-temura/",
"wikidata":"Q4421686","wikipedia":"ru:Сквер Эмира Тимура",
"opening_hours":"24/7",
"hours":{"raw":"24/7","all_day":true,"days":[[{"open":0,"close":1440}],[{"open":0,"close":1440}],
[{"open":0,"close":1440}],[{"open":0,"close":1440}],[{"open":0,"close":1440}],
[{"open":0,"close":1440}],[{"open":0,"close":1440}]]},
"open_status":{"open":true,"known":true,"all_day":true}},
"photo_hint":"/api/photo?wikidata=Q4421686",
"nearby":[…],
"geometry_url":"/api/geometry/112943"}Where a place came from#
osm.type is the letter the extractor stored, and the index holds places from three sources:
osm.type | tags.source | Extra tags |
|---|---|---|
n, w, r | — | — |
o | overture | overture:id, overture:category |
c | custom | custom:id, custom:categories, custom:gallery, custom:socials, custom:schedule, custom:attributes, custom:menu, status, image |
For o and c entries osm.id is a hash of the source record id, and osm.url is built by the same rule as for real OSM objects — anything that is not n or w gets /relation/ — so it points at nothing. Check tags.source before linking to it.
The custom:* values are raw JSON strings copied through as-is; custom:schedule is not turned into hours, and image / custom:gallery are relative paths that no endpoint on this server serves. Overture contact data does surface in details, because it is stored under the OSM key names (phone, website, email, contact:instagram, contact:telegram).
The /api/place/osm/{type}/{id} handler passes {type} through to the database unchecked, so /api/place/osm/o/701235510889211 resolves an Overture entry. The browser client's placeByOsm only types 'n' | 'w' | 'r'.
Opening hours#
When the entry has an opening_hours tag, details.hours is the parsed weekly schedule and details.open_status is its evaluation at the current time in Asia/Tashkent (tashkentNow in server.go — the client's clock and time zone play no part). Both come from internal/hours/hours.go.
hours:
| Field | Type | Notes |
|---|---|---|
raw | string | The tag as written. |
all_day | boolean | 24/7, 24 hours, 00:00-24:00 or круглосуточно. |
days | array of 7 arrays | Monday first. Each day is a list of {open, close} in minutes from midnight; close exceeds 1440 for a range that runs past midnight (20:00-02:00 is {1200, 1560}). A closed day is [], never null. |
unparsed | boolean | Present and true when no rule could be read. days is then all []. |
open_status:
| Field | Type | Notes |
|---|---|---|
open | boolean | |
known | boolean | false when the schedule was unparsed; open is then meaningless. |
all_day | boolean | Omitted when false. |
next_change | string | HH:MM of the next opening or closing. Omitted when there is none in the coming week. |
closes_soon, opens_soon | boolean | The change is within 60 minutes. Omitted when false. |
next_day | integer | Days ahead of the next opening — 1 is tomorrow. Omitted for today. |
The parser deliberately covers the patterns that dominate Uzbekistan's data rather than the full opening_hours grammar: rules separated by ;, day ranges and lists in English (Mo-Fr, Sa,Su) or Russian abbreviations (пн-пт), times with : or ., and off / closed. PH and SH are ignored. Anything else — month rules, week parities, sunrise — leaves the schedule unparsed.
A cafe tagged Mo-Su 09:00-23:00, fetched after closing time:
"hours":{"raw":"Mo-Su 09:00-23:00","all_day":false,
"days":[[{"open":540,"close":1380}],[{"open":540,"close":1380}],[{"open":540,"close":1380}],
[{"open":540,"close":1380}],[{"open":540,"close":1380}],[{"open":540,"close":1380}],
[{"open":540,"close":1380}]]},
"open_status":{"open":false,"known":true,"next_change":"09:00","next_day":1}GET /api/geometry/{id}#
The outline of a place as one GeoJSON Feature, for drawing a highlight. Coordinates are [lon, lat] at six decimal places. properties is {id, name, kind}. Served with Cache-Control: public, max-age=3600.
| Entry | geometry.type | properties.kind |
|---|---|---|
A street, or a nature entry with category nature.river | MultiLineString, one line per stored segment | The entry's kind |
A place whose osm.type is r | MultiPolygon built from the administrative boundary — outer ring first, then holes, per polygon | admin |
| Anything else with stored geometry (area POIs, lakes, parks) | Polygon, outer ring only | The entry's kind |
| Status | Body | When |
|---|---|---|
400 | bad id | Not an integer |
404 | not found | No such entry |
404 | no polygon | A relation-backed place whose boundary is not in the geocoder |
404 | no geometry | The entry is a point — most POIs, and places mapped as nodes such as city districts |
Only follow geometry_url from a place response; requesting geometry for an arbitrary id mostly yields no geometry.
curl "$UZMAPS/api/geometry/327434"{"type":"Feature",
"properties":{"id":327434,"kind":"admin","name":"Andijon viloyati"},
"geometry":{"type":"MultiPolygon","coordinates":[[[[71.97975,40.619067],[71.979203,40.619425],…]]]}}GET /api/categories#
The whole taxonomy, with labels in every language. No parameters. No Cache-Control header is set, but the content changes only with a server release; the browser client caches it for the page's lifetime.
{"groups":[{"id":"food","icon":"restaurant","color":"#E8712B",
"label":{"uz":"Ovqatlanish","ru":"Еда","en":"Food & drink"}}, …],
"categories":[{"id":"food.restaurant","group":"food","icon":"restaurant",
"label":{"uz":"Restoran","ru":"Ресторан","en":"Restaurant"},
"synonyms":["restoran","ресторан","restaurant","oshxona","ошхона","milliy taomlar"],
"rank":0.55}, …]}| Category field | Notes |
|---|---|
id | The value in a place's category. |
group | Not always the id's prefix — see the exceptions below the tables. |
icon | Sprite name. |
label | {uz, ru, en}. |
synonyms | Search terms in any script. Omitted when there are none. |
rank | Base importance 0–1. |
Groups#
The eighteen groups, from taxonomy.Groups:
id | icon | color | en |
|---|---|---|---|
food | restaurant | #E8712B | Food & drink |
shopping | shopping | #D9489C | Shopping |
health | health | #E0454B | Health |
education | education | #5B6CDB | Education |
finance | bank | #2E8B57 | Finance |
transport | transport | #3A7BD5 | Transport |
auto | fuel | #5A6B7C | Auto |
lodging | hotel | #8E5AD6 | Lodging |
culture | culture | #B4631E | Culture & sights |
religion | mosque | #5F7C5A | Religion |
leisure | park | #3E9B4F | Leisure & sport |
services | services | #6B7280 | Services |
beauty | beauty | #D46AA0 | Beauty |
office | office | #7A8699 | Offices |
nature | nature | #4F8A3C | Nature |
place | place | #4B5563 | Places |
street | street | #4B5563 | Streets |
address | address | #4B5563 | Addresses |
Categories#
All 108 ids from taxonomy.Categories, with their English label. Listed under the group they belong to, which is what group: filters and the group field use.
| Group | Categories |
|---|---|
food | food.restaurant Restaurant · food.cafe Cafe · food.fast_food Fast food · food.teahouse Teahouse · food.bar Bar · food.bakery Bakery · food.ice_cream Ice cream |
shopping | shopping.supermarket Supermarket · shopping.convenience Convenience store · shopping.mall Shopping mall · shopping.marketplace Bazaar · shopping.clothes Clothes · shopping.electronics Electronics · shopping.furniture Furniture · shopping.books Books · shopping.florist Florist · shopping.jewelry Jewelry · shopping.gift Gifts · shopping.shop Shop |
health | health.hospital Hospital · health.clinic Clinic · health.pharmacy Pharmacy · health.dentist Dentist · health.doctors Doctor · health.veterinary Veterinary |
education | education.university University · education.school School · education.college College · education.kindergarten Kindergarten · education.library Library · education.language_school Learning centre |
finance | finance.bank Bank · finance.atm ATM · finance.exchange Currency exchange |
transport | transport.airport Airport · transport.train_station Train station · transport.metro Metro station · transport.bus_station Bus station · transport.bus_stop Bus stop · transport.taxi Taxi |
auto | transport.parking Parking · auto.fuel Fuel station · auto.charging EV charging · auto.car_wash Car wash · auto.car_repair Car repair · auto.car_dealer Car dealer |
lodging | lodging.hotel Hotel · lodging.hostel Hostel |
culture | culture.museum Museum · culture.theatre Theatre · culture.cinema Cinema · culture.gallery Gallery · culture.attraction Attraction · culture.monument Monument · culture.viewpoint Viewpoint · culture.historic Historic site · culture.zoo Zoo |
religion | religion.mosque Mosque · religion.church Church · religion.synagogue Synagogue · religion.place_of_worship Place of worship |
leisure | culture.theme_park Amusement park · leisure.park Park · leisure.playground Playground · leisure.stadium Stadium · leisure.sports_centre Sports centre · leisure.fitness Fitness · leisure.swimming_pool Swimming pool · leisure.water_park Water park · leisure.garden Garden |
services | services.post Post office · services.police Police · services.government Government office · services.embassy Embassy · services.townhall Town hall · services.courthouse Courthouse · services.fire_station Fire station · services.notary Notary · services.laundry Laundry · services.toilets Toilets · services.telecom Telecom shop |
beauty | beauty.hairdresser Hair salon · beauty.salon Beauty salon |
office | services.coworking Coworking · office.company Company · office.it IT company · office.insurance Insurance · office.lawyer Lawyer · office.travel_agency Travel agency |
nature | nature.peak Peak · nature.spring Spring · nature.lake Lake · nature.river River · nature.beach Beach · nature.reserve Nature reserve · nature.cave Cave |
place | place.country Country · place.region Region · place.city City · place.town Town · place.district District · place.suburb Suburb · place.neighbourhood Neighbourhood · place.village Village · place.locality Locality · place.island Island |
street | street Street |
address | address Address |
Three things to be aware of:
- The prefix is not the group for
transport.parking(groupauto),culture.theme_park(groupleisure) andservices.coworking(groupoffice). Filter withgroup:autoto get car parks;group:transportwill not return them. streetandaddressare category ids with no dot; they are their own group.- A place's
categoryis not guaranteed to be in this list. The Overture mapper files funeral homes, storage, cleaning and repair businesses underservices.services, which the taxonomy does not define. For an undefined id the server derivesgroupfrom the prefix (services), uses the group icon, and returns an emptycategory_label. Treat an empty label as "generic member ofgroup".
GET /api/districts#
Administrative areas as label points, one FeatureCollection of Point features. No parameters. Served with Cache-Control: public, max-age=300.
The reason this exists: OSM maps districts and mahallas as boundary relations, so the vector tiles carry their outlines with no name attached and they cannot be labelled from tiles at all. There are only a few hundred, so one small document is cheaper than another tileset.
Levels included, and the minimum diagonal extent an area must have to be returned (a mis-tagged street can carry admin_level=6; a real district is kilometres across):
level | Meaning | Minimum extent |
|---|---|---|
| 6 | Viloyat / region | 2000 m |
| 7 | City | 2000 m |
| 8 | City district | 1200 m |
| 9 | Rural district | 1200 m |
| 10 | Mahalla | 150 m |
Feature properties:
| Property | Notes |
|---|---|
name | Primary name. |
level | As above. |
extent | Diagonal of the area's bounding box in metres. The style uses it to hold small mahalla names back until the area is large enough on screen to carry one. |
name:<key> | One per entry in the area's names map, so name:ru, name:uz-Cyrl, name:en, and also name:int_name, name:alt_name and the like. |
curl "$UZMAPS/api/districts"{"type":"FeatureCollection","features":[
{"type":"Feature",
"properties":{"name":"Бейнеу ауданы","level":6,"extent":252778,
"name:en":"Beynew District","name:ru":"Бейнеуский район","name:kk":"Бейнеу ауданы",
"name:int_name":"Beyneu","name:uz-Cyrl":"Бейнеу ауданы"},
"geometry":{"type":"Point","coordinates":[55.39638205789523,45.02939666418345]}},
…
]}The OSM extract's bounding box overlaps neighbouring countries, so some level-6 features — that first one is in Kazakhstan — lie outside Uzbekistan. Nothing in the endpoint filters them.
Photos#
Photos come from Wikidata and Wikimedia Commons and are the platform's one optional external dependency. They are on by default and switched off with uzmap serve --wikimedia=false; /api/status reports the state in its photos boolean.
GET /api/photo?wikidata=Q…#
| Status | Body | When |
|---|---|---|
200 | {"photo": {…}} | A photo was found |
200 | {"photo": null} | The item has no image, or the lookup failed (see caching below) |
502 | {"error":"bad wikidata id"} | wikidata missing or not matching ^Q\d+$ |
502 | {"error":"…"} | The Wikidata request itself failed |
404 | {"error":"photos disabled"} | Server started with --wikimedia=false |
Both 200 responses carry Cache-Control: public, max-age=86400.
The photo object:
| Field | Type | Notes |
|---|---|---|
url | string | Server-relative: /api/photo/img/Q4421686.jpg. |
source_url | string | The file's page on Commons. |
author | string | From Commons Artist, with HTML stripped. Omitted when empty. |
license | string | Commons LicenseShortName. Omitted when empty. |
license_url | string | Omitted when empty. |
title | string | File name without the File: prefix. Omitted when empty. |
width, height | integer | Dimensions of the original file as reported by Commons, not of the image the server serves. Omitted when zero. |
attribution | string | author · license · Wikimedia Commons, empty parts dropped. Always present. Display it next to the image. |
How a photo is resolved. The server asks Wikidata for the item's P18 claim and takes the first file name; asks Commons for that file's imageinfo at iiurlwidth=1200; downloads the 1200-pixel thumbnail (or the original if there is no thumbnail), reading at most 8 MB with a 15-second timeout per request; and writes the image to <data>/cache/photos/img/<qid>.jpg and the metadata to <data>/cache/photos/meta/<qid>.json. Concurrent requests for the same id wait for the first.
Negative results are cached too, including failures. If the item has no P18, or the Commons metadata or image download fails, an empty metadata file is written and every later request returns {"photo": null} from cache. Nothing expires it. Requesting the image URL is the one thing that retries: /api/photo/img/{qid}.jpg deletes the metadata file and resolves again when it has no image on disk. To clear a wrong negative by hand, delete meta/<qid>.json.
GET /api/photo/img/{qid}.jpg#
The cached JPEG, Content-Type: image/jpeg, Cache-Control: public, max-age=604800. The .jpg suffix is optional. Anything that cannot be served — photos disabled, no image, a malformed id — is a plain-text 404, so an <img> fails the normal way.
The key goes in the query string here. An <img src> is fetched by the browser itself; there is nowhere to attach an X-API-Key header. The server accepts ?key= on every path for exactly this reason (it also lets a MapLibre style carry the key on tile URLs). The browser client's photoUrl() does the work:
import { UzMapClient } from '@uzmaps/api';
const api = new UzMapClient({ baseUrl: 'https://maps.example.uz', apiKey: 'YOUR_KEY', language: 'en' });
const place = await api.place(112943);
if (place.tags.wikidata) {
const { photo } = await api.photo(place.tags.wikidata); // GET /api/photo?wikidata=Q4421686
if (photo) {
img.src = api.photoUrl(photo); // https://maps.example.uz/api/photo/img/Q4421686.jpg?key=YOUR_KEY
caption.textContent = photo.attribution;
}
}photoUrl appends the key only to server-relative URLs; an absolute url is returned untouched so the key never leaks to another host. Without the client, do the same by hand:
<img src="https://maps.example.uz/api/photo/img/Q4421686.jpg?key=YOUR_KEY" alt="Amir Temur square">Using the browser client#
Every endpoint on this page has a method on UzMapClient in @uzmaps/api. The client sends the key as an X-API-Key header on these calls; only photoUrl() uses the query form.
import { UzMapClient } from '@uzmaps/api';
const api = new UzMapClient({ baseUrl: 'https://maps.example.uz', apiKey: 'YOUR_KEY', language: 'ru' });
const { results } = await api.nearby(41.3111, 69.2797, { radius: 300, category: 'group:food', limit: 5 });
const place = await api.place(results[0].id); // PlaceDetails
const byOsm = await api.placeByOsm('n', 377979982); // same shape
const outline = place.geometry_url ? await api.geometry(place.id) : null; // GeoJSON Feature
const { groups, categories } = await api.categories();
const districts = await api.districts(); // GeoJSON FeatureCollectionplace, placeByOsm, geometry, categories, districts and photo are cached per URL for the lifetime of the client instance; nearby is not.