Cloud|

Places and maps

Find addresses, places, and routes through Core with shared budgets, caching, and attribution.

18 min read Updated 2026-10-22 #geo#addresses#places#capabilities

Use Core's geo Queries to suggest addresses, locate an address, find places, or calculate and edit a route. Built-in and third-party applications use the same contracts. Authenticated people, agents, app sessions, and service accounts follow the same path. The Assistant discovers these Queries through the capability catalog; MCP and CLI clients use the same capabilities.

An operator must enter the Geoapify key under Administration → Base settings → Places & maps (/admin/base/places). The section also holds the provider plan, the credit budgets, and this month's usage; see Administer places and maps. All provider calls use https://api-eu.geoapify.com. The endpoint cannot be changed. No provider key appears in geo responses. Use CloudMap for the shared light and dark map.

Choose a Query

Coordinates use { lat, lon } in degrees. Locale comes from request metadata, never from Query input. Inputs are strict: unknown fields are refused.

Core Query Input Result
address.autocomplete text (3–300 characters), optional limit (1–5, default 5), countryCode, type, bias point, and area suggestions (up to 5 addresses) and provenance
address.geocode Either text (1–300 characters) or fields; optional countryCode One address and provenance; ambiguity returns candidates in error details
address.reverse point One address and provenance
place.search categories (1–5), exactly one of near, area, or along; optional name (1–300 characters after trimming) and limit (1–20, default 20) places and provenance
map.style Optional theme (light or dark) Same-origin styleUrl and attribution links
route.calculate { plan }: 2–26 stops, route/per-leg options, optional optimization A self-contained route with resolved stops, independent legs, meters, seconds, polyline6, and provenance
route.update { route, changes } Proposed route, before, after, delta, and optional insertion details
geo.availability {} configured, person and cloud; each balance has exhausted and resetsAt

Autocomplete type accepts country, state, city, postcode, street, amenity, or locality. bias affects ranking. countryCode is an ISO alpha-2 country filter. An area is either:

json
{ "type": "bbox", "west": 9.9, "south": 48.3, "east": 10.1, "north": 48.5 }
json
{ "type": "circle", "point": { "lat": 48.4, "lon": 10 }, "radiusMeters": 1000 }

A bounding box must have west < east and south < north; split a search that crosses the date line. A circle needs a positive radius in meters. near ranks places by distance to a point.

Structured geocoding accepts line1, postalCode, city, stateRegion, and country, each at most 200 characters. At least one field must contain text. Core always requests 5 provider candidates; there is no caller-selected limit. A provider candidate is plausible when rank.confidence is at least 0.5. One plausible candidate is returned; several produce GEO_ADDRESS_AMBIGUOUS. Missing or lower confidence produces GEO_ADDRESS_NOT_FOUND. Confidence describes the provider's match, not a verified entrance or delivery address.

Addresses carry line1, postalCode, city, stateRegion, countryCode, point, formatted, confidence (number or null), buildingConfidence (number or null), and provenance. buildingConfidence is the provider's certainty that point is this building (rank.confidence_building_level); it is null when the result is not a house-number match. Store a point for a typed address only when this value is high; Contacts requires at least 0.9. Unavailable text components are empty strings.

Place categories are museum, cafe, restaurant, bar, pub, bakery, supermarket, pharmacy, hotel, accommodation, fuel, parking, charging, toilets, park, viewpoint, and attraction. GeoCategorySchema and GEO_CATEGORIES export the verified mapping.

Use optional name to ask the provider for places matching a name, such as "Wasserfall", within the required categories and geographic controls. This works for both area searches and route corridors. Outer whitespace is removed; case and internal whitespace are preserved in requests and cache keys. Omitting name keeps category-only search. Blank or longer than 300-character names are refused. Cloud returns provider matches rather than filtering a cached category result page by name.

A place carries id, name, provider categories, point, address, and provenance. It may include distanceMeters, openingHours (the raw OSM string), website, and phone. Missing opening hours are normal. No Place Details request is made. Results and cache entries are at most 256 KiB; oversized or malformed provider data fails rather than being returned partly.

Search along a route

Pass along: { polyline6, fromKm, toKm, bufferMeters? }. Positions refer to the full route. The window must be positive and at most 200 km. bufferMeters is 100–5,000 m, default 1,000 m.

The raw polyline is bounded to 102,400 ASCII characters, matching the provider's documented geometry byte budget. Core decodes it, cuts the requested distance window, then reduces it by minimum spacing to at most 4,000 points before applying iterative Douglas–Peucker to at most 2,000 points. Each stage doubles the tolerance until it fits. A dense full route is allowed within the raw string budget. A window starting beyond the route is unsupported; a window ending beyond it stops at the route's end.

Core sends a Places POST with filter.type: "polyline", widening the provider buffer by twice the simplification tolerance (rounded up, capped at 100,000 m), then filters hits against the original window with the requested buffer. It requests up to 20 candidates within the one-credit search budget, then applies the caller's result limit after filtering. If all 20 candidate slots are used and too few remain inside the requested corridor, truncated: true marks the search as incomplete. Narrow the distance window and search again; an empty truncated result does not mean that the corridor has no matches. It orders hits by atMeters, the position on the full route, and returns distanceToRouteMeters, a straight-line distance rather than a driving detour. Positions, distance windows, and corridor distances follow the shortest great-circle arc between consecutive route points, including near the poles and across the date line. Exactly antipodal segment endpoints do not define a unique arc and are unsupported; include an intermediate route point to choose the path. The result includes simplificationToleranceMeters.

Calculate and edit a route

Import geoRouteContract, GeoRoutePlanSchema, and GeoRouteSchema from @k2b/cloud/contracts. Call geoClient.calculateRoute({ plan }, caller) or geoClient.updateRoute({ route, changes }, caller) from a server. Core exposes these as route.calculate and route.update on the same capability, MCP, CLI, and mandate paths as address and place queries.

A plan has 2–26 stops with unique, case-sensitive id values and optional label values. Each stop supplies exactly one of point or address. An address uses the same shape and ambiguity rules as address.geocode. An ambiguity error identifies the stopId and up to five candidates; choose one candidate's point before trying again. Successful geocodes are charged even when a later address is ambiguous or no route is found.

ts
const result = await geoClient.calculateRoute({
  plan: {
    stops: [
      { id: "A", label: "Start", point: { lat: 48.4, lon: 10 } },
      { id: "B", address: { text: "Example Street 8, Example City" } },
    ],
    options: { mode: "car", avoid: ["highways"] },
  },
}, caller);

Options accept mode: "car" | "bike" | "walk" (default car) and avoid preferences tolls, ferries, and highways (default none). Tolls and highways require car; when changing mode, clear incompatible preferences explicitly. A preference is not a prohibition: the provider may use it if there is no alternative. Core always requests metric units, free_flow traffic, and independent stopover legs. There is no truck or live-traffic mode. Walking legs in a fixed order with known straight-line distance above 100 km are refused before routing. Optimization uses the order-independent total bound described below instead. The provider also enforces its walking-distance limit.

Use legOptions: [{ fromStopId, toStopId, options }] to override supplied options for an adjacent pair. Different options are calculated separately. With optimize: true, the provider orders intermediate stops using the route defaults while keeping the first and last fixed. Core then applies overrides to the outgoing leg of each departure stop. Thus the optimized order is based on defaults, rather than a mixed-mode optimization objective.

Save the entire returned route, including stops, legs, options, input hash, and provenance. Core stores no route. Each leg and the full route carry polyline6; geometry is simplified with a reported conservative simplificationToleranceMeters until the result fits the 256 KiB capability budget. Geometry simplification leaves distance and duration totals unchanged.

Pass the saved route with exactly one changes object:

Change Fields and behavior
insert stop, optional afterStopId. With a chosen gap, inserts there. Otherwise compares all gaps when there are at most three; for larger tours, compares the three smallest straight-line detours. Chooses the least added travel time, breaking ties by distance. Only the winning gap's leg is replaced. Candidates with an explicit empty Routing result (GEO_NO_ROUTE) are skipped. HTTP errors stop the chain; provider no-route HTTP behavior has not been verified live.
move stopId, toIndex (zero-based final index). Changes the order and recalculates affected legs.
optimize Reorders intermediate stops using defaults, fixes endpoints, then reapplies outgoing leg options.
options options, optional leg: { fromStopId, toStopId }. A leg change patches only that leg. A route-wide change patches defaults and clears overrides.

Options travel with their departure stop when moving or optimizing stops. An inserted stop inherits the replaced leg's options for both new legs. The update returns before and after summaries with stopIds, distanceMeters, and durationSeconds, plus signed delta values (after minus before). The caller chooses whether to save the proposal. Insertion also returns afterStopId, candidateGaps, and exhaustive; when there are more than three gaps, its geographic shortlist can miss the globally best gap.

Reserve estimated routing costs

This is a technical refinement of the hard budget limit: Cloud reserves conservatively before every call; Geoapify does not guarantee a cap on its side. There is no selectable distance allowance. For each leg, Core estimates road distance as straight-line distance × 2.0. This fixed factor is Cloud's technical policy, not a measured maximum detour or a provider guarantee. Current fixtures are synthetic; no measured Geoapify route has validated it.

Core reserves the full chain atomically before the first paid call, including geocoding, insertion candidates, optimization and replacement legs. Unknown address pairs use the maximum great-circle distance on Earth before applying the factor. This can require a large reservation: select autocomplete results with coordinates first when a request cannot fit the budget. Optimizing uses the greatest pair distance among the stops for each possible leg, so changing the order cannot invalidate admission. Insertion reserves the most expensive three possible candidates. After resolution, each call uses its resolved coordinates for the estimate and overrun check.

Reference pricing retains Geoapify's distance bands; these are a pricing formula, not a fixed reservation distance:

text
base = pairs + avoid preferences
if optimizing at least 4 stops:
  base += (stops - 2) * min(stops - 2, 10)
reference credits = base * max(1, ceil(distance meters / 500000))

If the reservation exceeds a configured total budget, Core returns GEO_LIMIT_EXCEEDED with limit: "reservationCredits", actual, and max in credits. Reduce the plan or ask an administrator to review the budget; a reset cannot raise the total limit. Insufficient remaining budget returns GEO_BUDGET_EXHAUSTED with requiredCredits, availableCredits, and shortfallCredits. Both refusals occur before paid work.

If the actual reference cost exceeds the call's estimate, Core returns GEO_COST_OVERRUN, stops later paid steps, and never retries automatically. Known costs are charged even beyond the reservation; unknown costs remain charged at their reserved amount. Further admissions see those costs. The administration page shows this month's overrun count; the audit event core.geo.route_cost_overrun records numerical costs without addresses. Review these costs before retrying. A greater distance within the same pricing band is not a cost overrun. Valid paid responses are cached, including overruns; reusing them costs zero, but the chain still requires budget admission.

Routing responses are limited to 26 × 256 KiB before decoding and compacted to the public 256 KiB result budget. Core requests JSON geometry without redundant encoded-provider polylines. Dense or very long routes can exceed this byte limit, fail with GEO_PROVIDER_FAILED, and retain their reserved charge as unknown. This does not imply a road-distance ceiling.

Handle budgets and errors

Costs use Geoapify credits, accounted internally as integer thousandths. Autocomplete, geocoding, and reverse cost one credit each. Places cost one credit per started 20 requested results, including empty or shorter responses. With the current 20-result limit, a place search costs one credit.

Core reserves address/place costs before calling the provider and settles the reference cost afterward. Routing reserves against the distance estimate described above. A complete result served from cache costs zero and creates no ledger write. The defaults are 90,000 credits per Cloud calendar month and 300 per access subject per day. Both periods use app.timezone, including daylight saving changes. Agents and service accounts count like people. Delegated sessions count against their resolved access subject. Concurrent reservations cannot exceed either limit. Known routing overruns can exceed a reservation and are recorded as spent.

geo.plan selects Free, API 10, API 25, API 50, API 100, API 250, or Custom. The plans provide 3,000/5, 10,000/12, 25,000/15, 50,000/20, 100,000/25, and 250,000/30 daily credits/requests per second respectively. Custom uses geo.custom_daily_credits and geo.custom_rps. The provider allowance is plan information; Cloud enforces its monthly and per-subject daily budgets. Set those with geo.monthly_budget_credits and geo.person_daily_credits. The daily budget cannot exceed the monthly budget, including on reset.

Calls fail immediately when the shared token bucket or per-function in-flight bound is full; there is no queue. The in-flight bound is the plan rate times the function deadline, rounded up. Deadlines are 2.5 seconds for autocomplete, 5 seconds for geocoding/reverse, 8 seconds for places, and 15 seconds for each routing request. Core retries at most once, only for a classified DNS, connection refusal, or certificate failure proving that nothing reached the provider. A timeout or abort after sending retains that request's reserved amount; unstarted chain steps are refunded. A process crash retains the open full-chain reservation.

Errors use the capability envelope's stable code, localized message, and structured details:

Code Details and recovery
GEO_NOT_CONFIGURED Ask an administrator to enter the key, or to replace an unreadable key or budget value, in Administration → Places and maps. No Valkey, budget, or provider work occurs.
GEO_FORBIDDEN Supply an authenticated actor and access subject.
GEO_BUDGET_EXHAUSTED scope (person or cloud) and resetsAt; wait for reset or ask the operator to review the limit.
GEO_BUSY retryAfterSeconds; wait, then retry. If it persists, check Valkey and provider capacity. A provider 429 also uses this code.
GEO_LIMIT_EXCEEDED limit, max, actual; reduce that field. For a minimum bound, max reports the required minimum.
GEO_UNSUPPORTED_OPTION Correct the input shape or choose one supported search mode/address representation.
GEO_ADDRESS_NOT_FOUND Refine the address, city, or country.
GEO_ADDRESS_AMBIGUOUS Up to 5 candidates; ask the person to choose or refine the address.
GEO_NO_ROUTE Adjust stops or mode; no connected route was found. Completed steps remain charged.
GEO_COST_OVERRUN estimatedDistanceMeters, actualDistanceMeters, reservedCredits, actualCredits, and costs; later paid steps stopped. Known costs were charged beyond the reservation when necessary. Review spend and the audit log before retrying.
GEO_PROVIDER_FAILED costs: { settledCredits, unknownCredits }; check known and uncertain spend before retrying. adminHint identifies an operator recovery step. On failed ledger settlement, costs reports the original reservation as unknown, while optional accounting: { reservationCredits, knownReferenceCredits, unknownReferenceCredits } exposes the costs that need reconciliation; no uncommitted overrun is claimed as settled.

Provider 429 errors also carry costs after a paid request. Provider bodies and transport error text are discarded. geo.availability is free, reads only the caller's counters, and makes no provider call. Missing keys or unreadable geo.* settings return configured: false without reading counters.

Administer places and maps

Only administrators open Administration → Base settings → Places & maps. Every part of the section uses the same admin API, /api/admin/core/geo; OAuth tokens need the read scope to view and write to change.

Request Effect
GET / Key state (none, set, unreadable) with when and by whom it was set, the plan, the budgets, the plan table, and app.timezone. Never the key.
GET /usage This month and today in credits, the share per function (tiles, places, route, address), how many access subjects reached their daily limit today, and this month's routeCostOverruns count.
PUT /key { apiKey } Checks the key with one reverse-geocoding lookup at the EU endpoint, then stores it. A refusal (422 geo_key_rejected) or an unreachable provider (502 geo_key_unverified) stores nothing; the previous key stays active. 400 geo_key_missing or geo_key_malformed stop before the provider is called.
DELETE /key Removes the key; maps, place search, and address suggestions turn off for everyone.
PUT /budget Saves plan, monthlyCredits, and personDailyCredits (plus customRps and customDailyCredits for the custom plan) together. Each field must be a whole number within the setting's bounds, and the daily limit at most the monthly budget. A refusal returns 400 geo_budget_invalid with a code per field and changes nothing.

The verification lookup costs about one credit at Geoapify and is not counted in Cloud's budget. The general settings API refuses geo.api_key, so a key always passes this check before it replaces another.

The page shows the month and today as bars. Today compares Cloud's use with the plan's daily allowance, which Geoapify treats as a soft limit. Usage is counted in Cloud's reference credits from the formulas above, not read from Geoapify's billing. The budgets stop Cloud from admitting new paid calls; they do not guarantee the provider's invoice.

Keep attribution with results

Every address, place, route, and route leg retains provenance: provider geoapify, an ISO computedAt, and attribution text Geoapify · OpenStreetMap with links to Geoapify and OpenStreetMap copyright. Keep provenance on stored objects. Show that linked attribution near lists that have no map, including address suggestions.

Valkey caches suggestions for 7 days, geocoding/reverse for 30 days, places and routes for 24 hours. Empty suggestion/place results last one hour. Errors are never cached. A hit keeps the original provenance and computedAt. Cache keys hash normalized input, request language, and adapter version: lowercase address text with collapsed whitespace and sorted categories. Routing preserves exact coordinates and case-sensitive stop IDs/labels, and sorts avoid preferences. Full route-result cache hits bypass the ledger; reused component calls are refunded during chain settlement. Only the autocomplete bias rounds to 0.01°; area, near, and reverse coordinates round to five decimal places. Circle radii stay exact. Route searches hash the full raw polyline6 route and include the window, buffer (with its default applied), sorted categories, limit, and request language. A cache failure skips the cache. A Valkey admission failure refuses paid calls with GEO_BUSY.

Paired App-PWA devices can use the platform's closed allowlisted autocomplete route. It checks the live pairing and visit and charges the owner's budget without turning the device into an employee or exposing employee credentials.

Call from a server

Import geoClient from @k2b/cloud/server and pass the same CapabilityCaller as other server capability calls. Methods are autocomplete, geocode, reverse, searchPlaces, calculateRoute, updateRoute, mapStyle, and availability. The client parses Core's response with schemas from @k2b/cloud/contracts.

ts
import { geoClient } from "@k2b/cloud/server";
import type { CapabilityCaller } from "@k2b/cloud/capabilities/server";

export async function findMuseums(caller: CapabilityCaller) {
  return geoClient.searchPlaces({
    categories: ["museum"],
    near: { lat: 48.4011, lon: 9.9876 },
    limit: 20,
  }, caller);
}

Pass caller authorization or cookie only to Core, plus request locale, request ID, and cancellation signal. Durable background work passes its revocable mandate and the owning app's workload credential. The shared broker preserves identity and mandate limits; never call the provider from an app. See deployment requirements for operator responsibilities.

Show a map

Import CloudMap from @k2b/cloud/geo/ui. Pass markers, lines, selection and height as for MapView in @k2b/ui. CloudMap supplies the Cloud style and required attribution. It inherits the UI locale and theme.

tsx
import { CloudMap } from "@k2b/cloud/geo/ui";

<CloudMap
  height={320}
  markers={[{ id: "museum", mark: 1, label: "Museum", point: { lat: 48.4, lon: 10 } }]}
/>

Pass the authorized SSR geo.availability result as availability when it is available. An unconfigured or exhausted snapshot retains the static pins without loading map assets. The host owns the explanatory message, retry, and the accessible list of saved places. Server output and the first client render use the same snapshot.

map.style and geoClient.mapStyle() return a style URL for non-Solid clients. Keep every returned attribution link visible. Styles, glyphs, and tiles use /api/geo; no browser request goes to Geoapify. Core serves the bundled IBM Plex glyphs locally. Tile requests use the fixed EU endpoint and the server-only key header.

Each tile costs 0.25 reference credits against the same monthly and personal daily budgets as address searches. Tile admission has a separate shared bucket at four times the plan's API rate and a 10-second deadline. A tile body is limited to 1 MiB: at the Free plan's maximum of 200 in-flight tile requests, buffered bodies are bounded to 200 MiB. Oversized or invalid responses fail visibly; they are never forwarded as map data.

Tiles have Cache-Control: private, max-age=604800; Valkey does not cache tile bodies. The map uses vector zooms 0–14 and scales the last level above that. Authentication and read scope apply to every asset route. Failed requests are not cached, and ambiguous transport failures retain the reserved credit.

Tile and glyph failures retain pins with Try again. CloudMap refreshes free availability after a failure and shows the person/day or Cloud/month reset time when a budget is exhausted. Core bundles its glyph files as server assets; production images need no runtime UI package or external font host.

Export a map image for print or PDF

CloudMap forwards the public MapView browser ref handle. Capture its complete fitted view with handle.snapshot({ signal }) after it is ready. The PNG includes the same flat Cloud basemap, road labels, saved geometry, pin marks and attribution. Capture introduces no provider request chain: it never starts, pans, refits or retries a map. Existing tile requests remain authenticated in Core, attributed to the original actor/access subject and subject to the same budgets. All provider traffic stays on the EU endpoint; no key or user credential reaches a renderer.

Use a fitted saved-document map rather than a panned interactive view. Reveal lazy maps before capture. Missing tiles or glyphs fail visibly; retry the map, then capture again. The MapView guide describes the complete-resource barrier, dimensions, cancellation and error codes.

For offline PDF, use geoMapSnapshotFingerprint from @k2b/cloud/contracts/geo-snapshot to bind each PNG to the complete validated saved GeoDocument. Submit binary images to an application-owned, authenticated endpoint with the saved document revision. The shared PDF preflight validates membership, fingerprints, dimensions, byte budgets and real decoding. The application still owns resource authorization, revision checks, omission reporting and linked attribution. A client's image is presentation input; it cannot attest that the geography is truthful and must never become canonical saved map data.

There is no server-side map renderer in this contract. Headless API or CLI PDF exports without browser image uploads must report unavailable map images and retain saved-source lists, or fail explicitly when map images are required. MapViewFrame remains a geometry fallback and does not supply a basemap PNG.

Use maps in Assistant

Assistant's native places and route widgets use these same permission-aware Queries and CloudMap. The conversation owns saved map state and proposals; Core remains stateless for routes. Follow-ups update the first widget, and saving to a notebook uses the shared map document format through Notebook capabilities. See Explore places and routes for selection, editing, recovery, and persistence behavior.

Suggest addresses in forms

Import AddressAutocomplete from @k2b/cloud/geo/ui for a street field. It calls address.autocomplete from the browser as the signed-in person, after 3 to 300 characters and a 250 ms pause, for at most 5 suggestions. A newer query waits for the open request instead of cancelling a call that Core may already have paid for, so one request per field is in flight.

tsx
import { AddressAutocomplete } from "@k2b/cloud/geo/ui";

<AddressAutocomplete
  label="Street and number"
  value={street()}
  onValueChange={setStreet}
  onPick={(address) => fillFields(address)}
  onOwnEntry={() => keepTypedText()}
  availability={ssrAvailability}
/>

The field never fills other fields itself. onPick receives the chosen GeoAddress with point and provenance; keep both with the stored address and drop them when the person changes a location field later. Next to suggestions, the list offers the typed text as its own entry: last on wide screens, first on phones so the on-screen keyboard cannot hide it; without suggestions it says that the typed text is saved as written. Arrow keys move, Enter takes the active entry, and Escape closes the list and keeps the typed text. The source line Geoapify · OpenStreetMap sits under the list.

Pass the SSR geo.availability result as availability. Without a configured key the field is a plain text input. An exhausted personal or Cloud budget, from the snapshot or from a refused request, shows one calm line with the reset time instead of suggestions and sends no further requests.

Cloud does no live navigation. geoNavigationHref from @k2b/cloud/geo/ui builds a link that hands a destination to the device's maps app: geo: on Android, Apple Maps on iPhone and iPad, and OpenStreetMap directions elsewhere. Pass a point when one is known, otherwise the address text. Choose the platform with geoNavigationPlatform(navigator.userAgent) after the first render, so server output and the first client render agree. The link carries only the destination and an optional name, and it leaves Cloud only when the person follows it.

Connect a coding agent

The Cloud skill provides compact working instructions. MCP supplies exact current documentation when details matter.

CLI command
bunx skills add https://docs.example.com

This command installs the working instructions published by this website in the selected coding agent.

For exact, current details, also connect the MCP server through one of the agent tabs.

Installation uses the open-source Vercel Skills CLI.

The skill and MCP complement each other: the skill describes workflows, while MCP supplies current documentation.