# Location (get_location)

The `get_location` tool returns geographic data. All actions return the same unified shape so you never have to case-split on the source:

```
{
  source: 'gps' | 'ip' | 'coords' | 'latest' | 'history',
  city: string | null,
  region: string | null,
  country: string | null,
  timezone: string | null,
  lat: number | null,
  lon: number | null,
  ip: string | null,       // echoed back when you pass an IP, or the caller's IP on 'me'/'gps'
  accuracy: number | null, // meters; populated only for GPS
}
```

Fields the action can't populate are `null`. Pick what you need; nothing is hidden behind the action name.

## Actions

| Action | When to use |
|---|---|
| `me` (default) | "Where am I?" - tries GPS if the user's browser advertised the capability, otherwise falls back to IP geo. The right default 95% of the time. |
| `ip` | Look up any IP. If `ip` param is omitted, uses the caller's IP. |
| `coords` | Reverse-geocode an arbitrary `lat`+`lng` into city/region/country. |
| `gps` | Force a live GPS round-trip to the user's browser. Fails clearly if no socket session or permission denied. |
| `latest` | Most recent stored location for this user (from the `user_locations` table). |
| `history` | Past stored locations - paginate with `count` (default 10, max 1000) or `hours`. |

## Examples

- "Where is the user right now?" → `action: 'me'`
- "What city is IP 8.8.8.8 in?" → `action: 'ip', ip: '8.8.8.8'`
- "Reverse-geocode 37.77, -122.41" → `action: 'coords', lat: 37.77, lng: -122.41`
- "Force a fresh GPS fix for navigation" → `action: 'gps', reason: 'to show nearby coffee shops'`
- "When was the user last in a different city?" → `action: 'history', hours: 168`

## GPS specifics

GPS only works when:
1. The user is in a **live browser session** (socket-connected - not REST-only, not a workflow).
2. Their browser has the Geolocation API (`navigator.geolocation`) - essentially all modern browsers.
3. They haven't previously denied permission (the client advertises `gpsPermission` on connect via the Permissions API).

If any of those fails for `action: 'me'`, the tool silently falls back to IP geo so you still get an answer. For `action: 'gps'`, you get a clear error instead of a fallback - use it when GPS specifically is required (navigation, high-accuracy fitness tracking, etc.).

Always pass a `reason` when calling GPS-capable actions - some clients show it to the user in the permission prompt.

## IP geo specifics

IP lookup uses the platform's local Geo DB (no network latency, no rate limits). Private IPs (`10.*`, `192.168.*`, `172.16-31.*`, `127.0.0.1`) return an error - there's no useful answer for those.

## User-side usage (outside the agent)

End users query their own location directly without going through the agent:

- **Web CLI**: `/location` (me), `/location 8.8.8.8` (IP), `/location 37.77 -122.41` (reverse geocode), `/location latest`, `/location history [count]`
- **Local CLI**: `gipity location [...]` - same auto-detect pattern, `--json` for structured output
- **REST (user-scoped)**: `GET /location/me`, `POST /location/ip`, `POST /location/coords`, `GET /location/latest`, `GET /location/history` - all return the unified shape. Auth: user JWT (`Authorization: Bearer <token>`).

---

## Calling location from a deployed app

Everything above is the agent-side `get_location` tool. A **deployed app** resolves a user's location through the app-scoped HTTP service instead - see [app-location](app-location.md) for the `/services/location/*` endpoints, the frontend and serverless-function patterns, and the X-Forwarded-For gotcha.

