Asking questions

Answers

Three endpoints, three questions. None return a coordinate, a distance, or a bearing — not in a response, and not in an error.

Every answer carries the same freshness fields:

FieldMeaning
asOfTimestamp of the fix the answer was computed from, or null
ageSecondsHow old that fix was, or null if there is no fix
reasonWhy the answer is unknown: stale, no_data, or boundary

asOf is not when you asked. We answer from the newest fix we hold, so inside with an asOf of 09:15 means as of 09:15 they were inside — read every answer that way before acting on it.

The age of a fix tells you nothing about where someone is, so you get it even when the answer is unknown.

Am I inside this zone?

POST /v1/answers/verify-zone
Authorization: Bearer cka_…
Content-Type: application/json

{
  "lat": 37.7749,
  "lon": -122.4194,
  "radius_m": 250,
  "label": "Shell on 5th",
  "max_age_s": 900
}
{
  "state": "inside",
  "reason": null,
  "asOf": "2026-08-24T09:15:00.000Z",
  "ageSeconds": 12
}

state is inside, outside, or unknown.

It's a POST so the zone travels in the body. A query string would put your business geography into access logs and browser history. It's a read, so it returns 200.

label is required, and the user reads it. It appears in their log as asked: near "Shell on 5th" (37.77,-122.42 r=250m)? → outside. Write it as the reason you're asking. zone_4718 looks suspicious; Shell on 5th looks like a fuel purchase being checked.

radius_m is 100–10,000 meters. The floor is the privacy control: under 100 m a zone stops being a question and becomes a way to pin someone down, so small requests are rejected rather than quietly widened. The ceiling is only a sanity bound — a large zone gives away very little, so err on the generous side.

Which place am I at?

GET /v1/answers/current-place?max_age_s=900
{
  "place": { "id": "…", "label": "Home", "kind": "home" },
  "state": "at_place",
  "reason": null,
  "asOf": "2026-08-24T09:15:00.000Z",
  "ageSeconds": 12
}

state is at_place, no_place, or unknown.

Only places shared with you count. no_place means "not at any place you can see" — not "not at a place".

When places overlap, the smallest one wins: "the gym", not "downtown".

Am I at this place?

GET /v1/answers/places/{place_id}/presence?max_age_s=900
{
  "state": "yes",
  "reason": null,
  "asOf": "2026-08-24T09:15:00.000Z",
  "ageSeconds": 12
}

state is yes, no, or unknown. There is deliberately no boolean beside it: a boolean has two values and this answer has three, so branch on state and handle unknown on its own. Don't fold it into no, or you'll tell users they left somewhere they never did.

A place not shared with you returns 404, exactly like one that doesn't exist.

Three things that will surprise you

There's a margin at the edge

We only answer inside or outside when the fix clears the circle's edge by max(50 m, the fix's own accuracy). Within that band the answer is unknown, with reason boundary.

Without it, yes/no questions would give up coordinates. Ask about a circle, then a slightly smaller one, then smaller again: the radius at which the answer flips is the user's distance from your center, and three of those from three centers is a position. The margin blurs the edge by more than you can usefully shrink the circle, so the search never converges. It also keeps a phone sitting on a boundary from flapping between answers.

yescan’t tellno
On Wi-Fifix accurate to ~10 mAnswers yes within 100 m of the centre
can’t tellno
On cell towers alonefix accurate to ~150 mAnswers yes nowhere — it has no middle

The dashed line is the 150 m circle in both. Only the phone changed. The green core is where we will commit to yes; the amber ring is where we refuse to guess; past it the answer is no.

Both circles are 150 m. The only thing that changed is how well the phone knows where it is — and that decides whether the place has a middle at all.

A phone on Wi-Fi usually knows its position to within about 10 m, so the band stays at its 50 m floor and a 150 m place answers yes across its inner 100 m. The same phone on cell towers alone can be 150 m out, the band grows to match, and the place has no middle left at all. This is why arrivals are far more reliable once someone's phone joins their home Wi-Fi: nothing about the place changed, the fix just got sharper.

Note the floor cuts both ways. Below 50 m of error, extra accuracy buys nothing — a 5 m fix and a 50 m fix produce exactly the same band, on purpose.

In practice: someone standing near the edge of your zone produces boundary repeatedly. Don't retry through it. Either widen the zone, or treat unknown as "no change" and keep your previous answer.

It also means a zone must be bigger than the margin to have an inside at all. A 100 m zone read by a 50 m-accurate fix has almost none. Size your zones accordingly — the places a user draws are held to a 150 m minimum for exactly this reason, and your own zones get no such floor.

Old data never gets a confident answer

If the newest fix is older than max_age_s, you get unknown / stale, even if that fix was well inside your zone.

max_age_s accepts 60–86,400 seconds and defaults to 900. A tighter window means more certainty and more unknown. Choose based on what a wrong answer costs: a door lock deserves 60 seconds, a photo tag can use a day.

unknown is a real answer

ReasonWhat happenedWhat to do
staleThere's a fix, it's too oldRetry later, or widen max_age_s
no_dataThis user has never sent a locationNothing to retry — check their setup
boundaryToo close to the edge to callKeep your previous answer

Limits

These three endpoints share a budget: 60 calls an hour and 300 a day per connection, combined. Your app also has a ceiling across every connection at once, which grows as more people connect it. verify-zone additionally limits how many different areas you may ask about per day.

Asking about one zone often is fine. Asking about many is not. See Errors and limits.

Previous
Purposes
Next
Places