Skip to content

Resolve a place, and read its day

Two endpoints for the question "what is going on here, on this day". POST /v1/resolve turns anything that identifies a place into candidates you can put on a map. POST /v1/context/day reads every service that a place and a date can satisfy, in one call.

Neither endpoint names a country and neither names a source. Coverage is declared in the answer, per reading and per section, so a thin result says why instead of looking empty.

Resolve anything that identifies a place

POST https://quicklookup.com/api/v1/resolve
Authorization: Bearer <your API key>

{ "input": "Harpa", "countryCodes": ["IS"], "limit": 5 }

You do not say what the input is. The service detects it, reports the reading it used as detected, and you can override that with reading when you know better.

Input looks like detected Resolved by Worldwide
8.8.8.8, 2001:db8::1 ip IP2Location ✅ city precision
way/456, relation/789, N123 osm_id Overpass ⛔ hosted extract
64.15, -21.94, 64°09'N 21°56'W coord_string local parser ✅
a@b.is email WHOIS / ccTLD ✅ country precision
https://…, example.is/path url WHOIS / ccTLD ✅ country precision
ruv.is domain WHOIS / ccTLD ✅ country precision
+354 555 1234 phone dialling code ✅ country precision
ge2kug geohash decoded locally ✅
IS, ISL country_code ISO index ✅
anything else query the gazetteer, then the country index ⛔ then ✅

Every candidate carries precision — one of country, region, municipality, locality, street, poi, address, point. Use it to label the pin. A phone number and a street address both return coordinates; only precision tells you that one of them means a country.

{
  "input": "Harpa", "detected": "query",
  "candidates": [{
    "label": "Harpa, 2, Austurbakki, Miðborg, Reykjavíkurborg, …",
    "lat": 64.1504, "lon": -21.9325,
    "precision": "poi",
    "bbox": [-21.9340, 64.1490, -21.9310, 64.1510],   // [minLon, minLat, maxLon, maxLat]
    "osm": { "type": "way", "id": "108335182" },
    "category": "amenity", "placeType": "theatre",           // OpenStreetMap class and type
    "placeGid": "d247943e-b4bc-50a4-9443-c01a121b817f",     // gid.is/osm/way/108335182
    "countryCode": "IS", "municipality": "Reykjavíkurborg",
    "region": "Höfuðborgarsvæðið", "timezone": "Atlantic/Reykjavik",
    "confidence": 0.61,
    "resolvedVia": ["nominatim.geocode"], "source": "fdw",
    "outOfScope": false
  }],
  "coverage": { "scope": "gazetteer", "detail": "…", "gazetteerCountries": ["IS"] }
}

Three behaviours worth designing against

outOfScope instead of an empty list. A result outside countryCodes is returned and flagged, not dropped. That is what lets an interface say "that is in Denmark — sign up for more" rather than "not found", which is indistinguishable from a typo.

coverage explains an empty answer. scope: "global" means the reading reaches everywhere. scope: "gazetteer" means it is limited to gazetteerCountries, which is whichever countries the OpenStreetMap services we host currently carry. Free text that matches no street still resolves if it names a country, in any of a few dozen languages.

geometry is opt-in. Pass "geometry": true to get each candidate's outline. It is off by default because a municipality's polygon is orders of magnitude larger than the rest of the answer, and most callers want a point.

Search as you type

A search box asks with PART of a name, and a whole-name search finds nothing for "Melaskól". Send "mode": "prefix" on every keystroke instead:

POST https://quicklookup.com/api/v1/resolve

{ "input": "Melaskól", "mode": "prefix", "near": { "lat": 64.1466, "lon": -21.9426 },
  "osmTags": ["amenity:school"], "limit": 5 }
  • Prefix, accents and case. Melaskól, melaskoli and Laugardalsl all find what they start; Bónus Laugavegi finds the Bónus on Laugavegur.
  • near ranks answers nearer the point first — a bias, never a filter. Send the centre of the map the visitor is looking at.
  • osmTags keeps only those OpenStreetMap tags: amenity:school, shop (any shop), leisure:sports_centre; several are OR-ed, and !highway excludes. Every candidate carries its category and placeType, so a list can show what kind of place each one is.
  • Order is the source's own — how well it matches weighed against the distance from near — and detected is always query: in this mode "Mel" is part of a name, not a country code.
  • Speed is tens of milliseconds, cheap enough for every keystroke; debounce by ~100 ms and drop answers to keystrokes that have been overtaken.

Two limits are the language's, not the index's. An inflection that changes a vowel is not matched — "Egilsstöðum" (the dative) does not find the town Egilsstaðir, though "Egilsst" does. And a kind of place written into a name is not a word of its own: "skóli" does not find Melaskóli, which is what osmTags: ["amenity:school"] is for. geometry is not available in this mode, and near/osmTags apply only to it.

Read a place's day

POST https://quicklookup.com/api/v1/context/day

{ "location": { "lat": 64.1504, "lon": -21.9325 },
  "date": "2026-10-03",
  "timezone": "Atlantic/Reykjavik",
  "radiusKm": 5 }

location takes coordinates, or { "resolve": "<anything /v1/resolve accepts>" }.

Two optional fields describe the caller rather than the place: at, the moment they ask from (rounded down to 15 minutes), and availableMinutes, the time they have from it. They reach every section whose source takes them — today the activity rating — and change nothing else.

The sections are discovered, not listed. Every published service you can see is offered the day and the place; those whose contract can be satisfied from those two facts run, and those that cannot say which input they were missing. A source published tomorrow appears here by itself. Narrow the read with include: ["<section key>"].

Each section answers three questions a collage is misleading without.

status — why is this section empty?

Status Means
ok It answered, with rows
empty It answered; there is nothing for this place and day
unavailable It did not answer — down, throttled, or past the deadline. error says which
out_of_range The day is outside what the source covers
not_applicable It needs more than a place and a day; missingInputs names what
pending Only with Prefer: respond-async: not read yet. pending says why and when to ask again

empty and unavailable must never render the same. Nothing is happening and we could not look are different statements about the world.

influence — what did "at this location" mean?

"influence": { "type": "station", "note": "The archive is a set of stations; the nearest
                reporting cell answers, and it may be some way off." }

point, radius (with radiusMeters), station, region or country. It is decided by QuickLookup and echoed back, so a map draws exactly the reach that was used. A client cannot know that a cruise call draws visitors 25 km inland, that a hazard warning covers a forecast region rather than a point, or that a source with no location input is answering for the whole country — so it is not asked to guess.

An in-person event is found only where it is held. An event collection that publishes location is a radius section even when it is stored and takes no coordinates as an input. It returns the events whose sites lie within reach of the place. It also returns the events held at no place (attendanceMode: none), such as a pay day or Black Friday, which reach everywhere. A sheep round-up in Skagafjörður is therefore not part of Reykjavík's day. A collection that publishes no locations (a TV schedule) still answers for the whole country.

A section is only shown when it is about the place you asked for

The map's extent and the data's extent are different things. The resolver is worldwide at country precision; most of the sources hold one country's data. A source that cannot be shown to cover the place comes back not_applicable with the reason — never ok with another country's data, which is worse than no answer:

{ "key": "vegagerdin-traffic-counts-daily", "status": "not_applicable",
  "note": "This source holds data for IS and cannot be shown to cover JP." }

Two things are checked, and neither is a hand-kept list: a source whose country input falls back to a default is answering about that country, and a source that takes neither a location nor a country can only be about the countries we hold data for (QUICKLOOKUP_DATA_COUNTRIES). Sources that compute from your coordinates — sun and moon, seasons, weekends — answer anywhere, and so does anything that accepts the country you asked about, like holidays.

availability — and the note that stops it being misread

A declared window is a property of the relation, not of your coordinate. A weather archive can declare years of coverage and still hold nothing at a given point, because it is a set of stations and the nearest one may never have reported. When a section is empty inside its own declared window, note says so. Do not render that as an absence of weather.

Timing

A day is local. Combining a date with UTC at a place that is not on UTC reads an interval shifted by the offset — picking up a neighbouring day at one end and dropping the day's own at the other. The window is built in the place's own zone; on a clocks-forward day it is correctly 23 hours long. from, to and timezone come back so you can show what was read.

Sections run in parallel under one budget for the whole call, not one per section. A section that passes the deadline is reported unavailable and the response is not held for it, so the slowest source costs its own section rather than the day.

Show the day at once, and fill it in

A day nobody has asked about yet needs its sources called, and some take seconds. Send Prefer: respond-async and the day is answered within a second: every section that is stored or computed is there, and the rest come back pending, with the whole answer as 202 Accepted:

{ "sectionsPending": 3,
  "pending": { "reason": "sourcing", "retryAfterMs": 1400, "message": "…" },
  "sections": [
    { "key": "ctx-weather-metrics", "status": "pending",
      "pending": { "reason": "sourcing", "retryAfterMs": 1400,
                   "message": "One moment, this data is being sourced." } },
    { "key": "ctx-celestial", "status": "ok", "items": [ … ] } ] }

Render what is there, then ask again after retryAfterMs for just the pending keys (include: ["ctx-weather-metrics", …]); the sections that were still being read are kept for that ask, so it is answered from memory. Repeat while any is pending. See Answer now, fill in later.

One section that is not a service

events-near is the fan-out across every visible event feed at once, which is how a person reads "what is on near here". Occurrences carry distanceMeters and matchedSite. If a feed could not be read, note names it and its reason — the rest of the answer is still served.

Activities (spec 173)

Three sections say what the day is good for. All are ordinary sections: they are discovered like any other, and no caller or route changed to add them.

  • Activity rating (ctx-activity-rating): one item per activity, 43 of them in seven groups (sport, nature, culture, food, leisure, family, events), each rated 0–100 for the place, the moment (at) and the time the caller has (availableMinutes). The rating is a weighted geometric mean of five factors, each reported with its score:
  • weather_score — the best weather window inside the caller's time, at the place the activity would happen; indoors it counts the other way — the better the day outdoors for the place and season, the lower (a calm, sunny day counts against the cinema);
  • daylight_score — the daylight left against the activity's duration (darkness for aurora, the golden hours for photography);
  • service_score — the service chosen within reach: travel time, whether it is open for the time needed (OpenStreetMap opening hours; unknown counts 0.8), the choice around it; for an event, the event;
  • fit_score — the time the caller has against the duration and the travel there and back;
  • timing_score — how customary the activity is at that hour, weekday or weekend (measured: Reykjavík's pool gates, path and nightlife counters; norms for meals, cafés, museums and shops). The visit is placed at the most customary slot inside the caller's time, so a barbecue asked at 15:00 with the evening free is rated for 18:00 — but always in the light the activity needs first: a December walk goes at 14:00, not at its customary but dark 17:00, and a golden-hour photograph in the golden hour. Absent for activities with no profile.

How places are chosen. One model, the same for every activity: - what a place is — its OpenStreetMap functions. tourism=attraction is a marker, not a function: a hot-dog stand that carries it is still a fast-food stall. A sports centre is what its sport=* says, so a sports centre for swimming is a pool, not a gym; - how well it serves the activity (serves, 0–1) — a graded table per activity: fully, often or sometimes. Area activities are graded by size against a standard on a log scale: a walk wants 20 ha, and a green under 2 ha serves no walk. Private places serve nothing; - what it is worth (quality, 0.5–1) — evidence only: Wikipedia prominence (Nominatim, only for a place the map links to Wikipedia or Wikidata — an unlinked place's importance is Nominatim's default for its kind of feature, not its fame), a Wikidata item, a name, and Ferðamálastofa's assessment of the 2,191 sites in Iceland worth visiting (assessed: 2 or 3). The assessed sites are also places of their own: a church a saga is told about, a view, a known walking route.

Service is quality × how well it serves, together with travel and opening. The activities are answered together, not one by one (maximal marginal relevance): the one surest of its answer chooses first, and a place already chosen counts 15 rating points less for the next. The pool answers swimming, and the hot soak goes to a lagoon unless nothing comes within that margin. shared_with says when one place does answer several. The travel is the real trip: the drive to where the road reaches, then the walk in (walk_min). A place no road or path reaches (an island reserve) is not offered. Hiking offers named routes with their length and start; cinema and theatre are rated from what is on (the calendar), never from opening hours.

The weather weighs as much as every other factor together, outdoors and indoors: a storm sinks a walk and a fine day sinks the museum by the same measure. It is read where it can change the answer: when a place farther away would beat the chosen one in perfect weather, its weather is read too (a gale at the nearest cape sends bird watching to a calm spot inland).

options lists up to ten places or events for the activity. The chosen one comes first (chosen: true), even when it is not possible: it then carries the why and next_change. The others that are possible follow, best first. options_total counts every possible one within reach. Each option carries: - its own rating, band, reason and factors; - name, type (the place's OSM function), served_by (what decided it serves: an OSM function or the assessors' category, site=…), OSM reference and point, website; - travel: mode, travel_min, walk_min; - opening: opening_hours, open_status, next_change; - its visit: slot_start / slot_end; - serves, quality, assessed and shared_with; - fee: paid, free, or absent when the map does not say. It is reported and never rated: OSM says whether there is a fee for about a third of baths and 4 % of attractions, and almost never how much, and dearer is not worse for everyone.

One place mapped twice is one option. An option more than 10 km from every place whose weather was read carries no weather reading. factors.weather is then absent, not assumed, and such options come after those whose weather is known: show them as "weather not checked". facility_tags lists the OSM functions that serve the activity, with how well ([{tag, serves}]), so a map can show every place of the activity's kinds.

band is excellent (80+), good (60+), fair (40+), poor (20+), not_recommended (1–19) or not_possible (0). Only a known impossibility makes it 0, named in reason: no_facility, closed (with next_change), no_daylight_left, no_darkness_left, no_golden_hour_left, not_advised, out_of_season, not_enough_time (the caller's time is too short), too_late (the activity's own day is over — a museum at 20:30 — with next_change its start tomorrow), no_event. Where the map, routing or weather does not reach the place, the rating is absent and reason is unavailable — show that differently from 0. limiting_factor names what lowered it most; weather_rank is the weather's rank against the place's own history.

Each item names what was chosen: facility_name, facility_type, OSM identity and coordinates, travel_min, open_status and alternatives; for an event, event_name, event_start, event_end and event_url (its source). Events come from the calendar's public feeds, and one is offered only when it starts and ends inside the caller's time, travel included.

Aurora is rated on viewing conditions only (dark, clear, calm, the moon). Whether an aurora is active needs a geomagnetic source CTX does not have yet; every aurora item says so in missing. Present it as "good conditions to look", never "you will see it".

  • Activity weather (ctx-activity-weather): one item per activity. There are 12: golf, hiking, barbecue, outdoor swimming, beach/picnic, kayak/SUP, cycling, running, an outdoor event, a scenic drive, aurora and skiing. Each item carries:
  • verdict, against universal limits: suitable, not_suitable, not_advised (an official orange or red warning), out_of_season, unknown or unavailable;
  • the best window (window_start–window_end), what limited it (limiting_factor) and a day_score from 0 to 1;
  • rank, 0–100 against the same place on the same days of other years (±15 days), with base_rate and rare_here. Show the verdict and the rank together: a chronically windy place can be not_suitable by the universal limits and still at the top of its own range.
  • Activity opportunity (ctx-activity-opportunity): one item per activity, answering "where, from here". It names the best facility within reach, found in OpenStreetMap (facility_name, facility_type, OSM id, coordinates), with travel_min, access_factor and opportunity (the day score read at the facility, times access). reason is ok, no_facility (the place is covered but nothing is within reach) or unavailable (the map or routing does not cover the place). The three never stand in for one another.

Every item's sources and missing say what it was read from and what it lacked. The relative readings and the rank compare with the nearest station's long record over the last 25 years, and the model day is first brought onto that station's scale. Querying the relations directly, years (1–35) and correction (linear, quantile, none) change both; the day context uses the defaults, and every item echoes them. Activity-specific inputs with no source yet (snow depth, lift status, geomagnetic activity, road status) make an item partial; they never remove the activity. A place outside today's coverage answers unavailable with the missing sources named, never an empty section.

Ranking, and what confidence means

Candidates come back best first, ordered by confidence, which measures how well a candidate answers what was typed — not how prominent the place is:

Score Meaning
1.00 the name is exactly what was typed
0.90 the name starts with what was typed — what typeahead is doing
0.75 a whole word of the name is what was typed
0.55 the name contains it
0.30 no textual match; the source returned it for another reason

Prominence adds at most 0.05 on top, so it orders ties without ever crossing a band. Accents and case are folded, so island scores the same as Ísland.

name is the short name on its own and houseNumber is its own field, so nothing needs to split label on commas.

Repeats are cheap, briefly

Both endpoints sit behind a short, bounded, per-replica cache (60 s by default). It exists because a lookup box asks the same question on every keystroke and re-render, and each one reaches CTX where a foreign-data query runs Python inside PostgreSQL. It is not a freshness mechanism — the mirror underneath decides that — and a day that could not be read completely is never cached, so an outage is never held in front of the next caller.

Where to go next