Asking questions

Derived history

Where someone has been, as place names and time windows. This is what a journal, photo or timeline app needs, and why such an app never has to ask for coordinates.

Visits

GET /v1/visits?from=2026-08-24T00:00:00Z&to=2026-08-25T00:00:00Z&limit=100
{
  "visits": [
    {
      "arrival": "2026-08-24T09:15:00.000Z",
      "departure": "2026-08-24T10:40:00.000Z",
      "place": { "id": "…", "label": "The gym", "kind": "gym" }
    },
    {
      "arrival": "2026-08-24T12:02:00.000Z",
      "departure": null,
      "place": { "id": "9f2c…", "label": "Home", "kind": "home" }
    }
  ],
  "nextCursor": "MjAyNi0wOC0yNFQxMjowMjowMFo…"
}
ParameterDefaultNotes
from, tononeISO timestamps, both optional
limit100max 500
cursornonepass nextCursor back unchanged

nextCursor is null on the last page. Don't parse it.

You see only the places shared with you

Every visit returned is at a place the user shared with your app. place is never null. Stops anywhere else are not returned, and are not distinguishable from not having happened.

A page can hold fewer than limit visits and still have more to come. Filtering happens partly in the database and partly after fetching, so some capacity in a page can be spent on rows that are then dropped. Page until nextCursor is null — a short page means nothing, an absent cursor means you are done. Never infer "no more results" from a count.

This used to work differently: unshared stops came back as time windows with no geography, on the reasoning that a window without a place reveals nothing. It reveals more than it looks. "Somewhere, 22:40 → 07:15" next to a shared Home says the user slept elsewhere — a fact about their movements they never handed over, inferred without a single coordinate.

Your timeline is therefore partial by design, and should say so rather than implying the gaps are idle time. A gap means "not at a place you were given", which covers travelling, being somewhere unshared, and the phone recording nothing at all — you cannot tell which, and neither can we tell you.

History starts when the place was shared

Sharing a place is consent to be observed from that moment on. Visits from before it are not returned, so connecting today does not hand you a year of someone's history, and a share made by mistake and withdrawn a minute later costs a minute.

A user can lift that per place, from their Connections screen, by explicitly including earlier visits. Until they do, treat the beginning of your data as the beginning of the relationship, not the beginning of their life.

Open visits

departure is null while the visit is still going. Check for it before computing a duration — null means "hasn't left", not "left at time zero".

Where was I at this moment?

GET /v1/lookup/place?at=2026-08-24T09:30:00Z&tolerance_s=900
{
  "place": { "id": "…", "label": "The gym", "kind": "gym" },
  "matched": true,
  "asOf": "2026-08-24T09:29:12.000Z",
  "deltaSeconds": 48
}

We take the nearest fix on either side of at and answer with the place it falls inside. asOf is that fix's timestamp; deltaSeconds is the gap between the two — ask about 09:30:00, get a fix from 09:29:12, and it reads 48. Treat it as your confidence in the answer: 5 is effectively the moment you asked, 800 means the nearest thing we held was thirteen minutes away. It's unsigned, so it won't tell you whether that fix came before or after at.

tolerance_s defaults to 900 and caps at 86,400. Outside it you get matched: false with place and asOf both null.

matched: false with a non-null asOf means something different: there was a fix close enough in time, but it wasn't inside any place you can see.

Things to know

Visits come from the phone. They're CoreLocation visit events — the OS deciding someone stopped. They're sparse and biased toward real stops. No visit for driving through, none for a five-minute errand. Don't expect a continuous timeline.

Labels are read fresh each time. Rename a place and past visits come back with the new name. Archive it and past visits keep the old one while new ones stop matching.

History fills in later. A phone that was offline uploads its backlog on reconnect, so a range you already fetched can gain rows. Re-fetch recent windows rather than assuming a day is final.

Previous
Places