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,melaskoliandLaugardalslall find what they start;Bónus Laugavegifinds the Bónus on Laugavegur. nearranks answers nearer the point first — a bias, never a filter. Send the centre of the map the visitor is looking at.osmTagskeeps only those OpenStreetMap tags:amenity:school,shop(any shop),leisure:sports_centre; several are OR-ed, and!highwayexcludes. Every candidate carries itscategoryandplaceType, 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— anddetectedis alwaysquery: 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,unknownorunavailable;- the best window (
window_start–window_end), what limited it (limiting_factor) and aday_scorefrom 0 to 1; rank, 0–100 against the same place on the same days of other years (±15 days), withbase_rateandrare_here. Show the verdict and the rank together: a chronically windy place can benot_suitableby 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), withtravel_min,access_factorandopportunity(the day score read at the facility, times access).reasonisok,no_facility(the place is covered but nothing is within reach) orunavailable(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
- Contracts, paging, and errors for the shared error shape.
- Fetch events as an agent for the event feeds behind
events-near. - API reference and test console for the full schemas.