Geolocation Block

Complete location services for any application
The Geolocation Block provides a comprehensive suite of location-based services. Store and manage locations, define geofences, calculate distances, perform geocoding, search for nearby points, and optimize routes—all through a simple, unified API.
Features
| Feature | Description |
|---|---|
| Location Storage | Store and manage points of interest with rich metadata |
| Geofencing | Define virtual boundaries and trigger events on entry/exit |
| Geocoding | Convert addresses to coordinates and vice versa |
| Reverse Geocoding | Get addresses from coordinates |
| Nearby Search | Find locations within a radius or bounding box |
| Distance Calculations | Calculate distances with multiple formulas |
| Route Optimization | Find optimal paths between multiple waypoints |
| Area Definitions | Define complex polygons and service zones |
| Real-time Tracking | Track moving entities with geofence triggers |
API Endpoint
| Service | URL |
|---|---|
| Geolocation | https://geolocation.api.us.23blocks.com |
Environment Routing: One URL, one environment. All shared-platform API keys are live keys:
pk_live_*/sk_live_*→ your production tenant. Need a separate staging environment? It is available as a paid add-on on deployments in your own cloud account — see 23blocks in a Box.
Quick Start
Installation
npm install @23blocks/sdk
Initialize the Client
import { create23BlocksClient } from '@23blocks/sdk';
const client = create23BlocksClient({
urls: { geolocation: 'https://geolocation.api.us.23blocks.com' },
apiKey: 'your-api-key', // Your live API key (pk_live_*)
});
Basic Usage
// Create a location
const location = await client.geolocation.locations.create({
name: 'Downtown Office',
description: 'Main headquarters',
latitude: 40.7128,
longitude: -74.0060,
category: 'office',
address: {
street: '123 Main Street',
city: 'New York',
state: 'NY',
postal_code: '10001',
country: 'US'
},
metadata: {
capacity: 500,
parking: true,
accessible: true
}
});
// Find nearby locations
const nearby = await client.geolocation.locations.nearby({
latitude: 40.7128,
longitude: -74.0060,
radius: 5000, // meters
category: 'restaurant'
});
// Create a geofence
const geofence = await client.geolocation.geofences.create({
name: 'Downtown Zone',
type: 'circle',
center: { latitude: 40.7128, longitude: -74.0060 },
radius: 1000,
triggers: ['enter', 'exit', 'dwell'],
dwell_time: 300 // 5 minutes
});
API Reference
Base URL
https://api.23blocks.com/v1/geolocation
Authentication
All requests require authentication headers:
X-App-Id: your-app-id
X-Api-Key: your-api-key
Authorization: Bearer <user-token> # For user-specific operations
Locations API
Manage points of interest with rich metadata, categories, and search capabilities.
Create Location
Create a new location with coordinates and metadata.
POST /v1/geolocation/locations
Request Body:
{
"data": {
"type": "locations",
"attributes": {
"name": "Central Park Store",
"description": "Flagship retail location",
"latitude": 40.7829,
"longitude": -73.9654,
"altitude": 10.5,
"category": "retail",
"subcategory": "flagship",
"address": {
"street": "768 5th Avenue",
"city": "New York",
"state": "NY",
"postal_code": "10019",
"country": "US"
},
"contact": {
"phone": "+1-212-555-0123",
"email": "centralpark@store.com",
"website": "https://store.com/centralpark"
},
"hours": {
"monday": { "open": "09:00", "close": "21:00" },
"tuesday": { "open": "09:00", "close": "21:00" },
"wednesday": { "open": "09:00", "close": "21:00" },
"thursday": { "open": "09:00", "close": "21:00" },
"friday": { "open": "09:00", "close": "22:00" },
"saturday": { "open": "10:00", "close": "22:00" },
"sunday": { "open": "11:00", "close": "19:00" }
},
"metadata": {
"parking": true,
"wheelchair_accessible": true,
"accepts_credit_cards": true,
"wifi": true,
"outdoor_seating": false
},
"tags": ["flagship", "premium", "manhattan"],
"status": "active"
}
}
}
Response:
{
"data": {
"id": "loc_abc123def456",
"type": "locations",
"attributes": {
"name": "Central Park Store",
"description": "Flagship retail location",
"latitude": 40.7829,
"longitude": -73.9654,
"altitude": 10.5,
"geohash": "dr5ru6",
"category": "retail",
"subcategory": "flagship",
"address": {
"street": "768 5th Avenue",
"city": "New York",
"state": "NY",
"postal_code": "10019",
"country": "US",
"formatted": "768 5th Avenue, New York, NY 10019, US"
},
"contact": {
"phone": "+1-212-555-0123",
"email": "centralpark@store.com",
"website": "https://store.com/centralpark"
},
"hours": {
"monday": { "open": "09:00", "close": "21:00" },
"tuesday": { "open": "09:00", "close": "21:00" },
"wednesday": { "open": "09:00", "close": "21:00" },
"thursday": { "open": "09:00", "close": "21:00" },
"friday": { "open": "09:00", "close": "22:00" },
"saturday": { "open": "10:00", "close": "22:00" },
"sunday": { "open": "11:00", "close": "19:00" }
},
"metadata": {
"parking": true,
"wheelchair_accessible": true,
"accepts_credit_cards": true,
"wifi": true,
"outdoor_seating": false
},
"tags": ["flagship", "premium", "manhattan"],
"status": "active",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
}
}
List Locations
Retrieve locations with filtering, pagination, and sorting.
GET /v1/geolocation/locations
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
category | string | Filter by category |
subcategory | string | Filter by subcategory |
tags | string | Comma-separated list of tags (any match) |
status | string | Filter by status: active, inactive, pending |
country | string | Filter by country code (ISO 3166-1 alpha-2) |
state | string | Filter by state/province |
city | string | Filter by city |
search | string | Full-text search in name and description |
page[number] | integer | Page number (default: 1) |
page[size] | integer | Items per page (default: 20, max: 100) |
sort | string | Sort field: name, created_at, -created_at |
Example Request:
curl -X GET "https://api.23blocks.com/v1/geolocation/locations?category=retail&city=New%20York&page[size]=10" \
-H "X-App-Id: your-app-id" \
-H "X-Api-Key: your-api-key"
Response:
{
"data": [
{
"id": "loc_abc123def456",
"type": "locations",
"attributes": {
"name": "Central Park Store",
"latitude": 40.7829,
"longitude": -73.9654,
"category": "retail",
"status": "active"
}
}
],
"meta": {
"total_count": 45,
"page_count": 5,
"current_page": 1,
"per_page": 10
},
"links": {
"self": "/v1/geolocation/locations?page[number]=1&page[size]=10",
"next": "/v1/geolocation/locations?page[number]=2&page[size]=10",
"last": "/v1/geolocation/locations?page[number]=5&page[size]=10"
}
}
Get Location
Retrieve a specific location by ID.
GET /v1/geolocation/locations/:id
Response:
{
"data": {
"id": "loc_abc123def456",
"type": "locations",
"attributes": {
"name": "Central Park Store",
"description": "Flagship retail location",
"latitude": 40.7829,
"longitude": -73.9654,
"geohash": "dr5ru6",
"category": "retail",
"subcategory": "flagship",
"address": {
"street": "768 5th Avenue",
"city": "New York",
"state": "NY",
"postal_code": "10019",
"country": "US",
"formatted": "768 5th Avenue, New York, NY 10019, US"
},
"status": "active",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
}
}
Update Location
Update an existing location.
PATCH /v1/geolocation/locations/:id
Request Body:
{
"data": {
"type": "locations",
"attributes": {
"description": "Updated flagship retail location",
"metadata": {
"parking": true,
"wheelchair_accessible": true,
"renovated": "2024"
}
}
}
}
Delete Location
Delete a location (soft delete by default).
DELETE /v1/geolocation/locations/:id
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
permanent | boolean | Permanently delete (default: false) |
Nearby Search API
Find locations within a specified distance from a point.
Search Nearby
Find locations near a given point.
POST /v1/geolocation/locations/nearby
Request Body:
{
"data": {
"type": "nearby_search",
"attributes": {
"latitude": 40.7580,
"longitude": -73.9855,
"radius": 2000,
"unit": "meters",
"category": "restaurant",
"tags": ["italian", "fine-dining"],
"filters": {
"metadata.outdoor_seating": true,
"metadata.price_range": { "$lte": 3 }
},
"include_distance": true,
"sort_by": "distance",
"limit": 20
}
}
}
Parameters:
| Parameter | Type | Description |
|---|---|---|
latitude | number | Center point latitude (-90 to 90) |
longitude | number | Center point longitude (-180 to 180) |
radius | number | Search radius |
unit | string | Distance unit: meters, kilometers, miles, feet |
category | string | Filter by category |
tags | array | Filter by tags (any match) |
filters | object | Custom metadata filters with operators |
include_distance | boolean | Include distance in response |
sort_by | string | Sort by: distance, name, rating |
limit | integer | Maximum results (default: 50, max: 500) |
Response:
{
"data": [
{
"id": "loc_xyz789",
"type": "locations",
"attributes": {
"name": "Trattoria Milano",
"latitude": 40.7590,
"longitude": -73.9840,
"category": "restaurant",
"address": {
"formatted": "152 W 44th St, New York, NY 10036"
}
},
"meta": {
"distance": 245.7,
"distance_unit": "meters"
}
},
{
"id": "loc_def456",
"type": "locations",
"attributes": {
"name": "Bella Italia",
"latitude": 40.7565,
"longitude": -73.9870,
"category": "restaurant"
},
"meta": {
"distance": 412.3,
"distance_unit": "meters"
}
}
],
"meta": {
"total_found": 15,
"search_center": {
"latitude": 40.7580,
"longitude": -73.9855
},
"search_radius": 2000,
"search_unit": "meters"
}
}
Search Within Bounds
Find locations within a bounding box.
POST /v1/geolocation/locations/within-bounds
Request Body:
{
"data": {
"type": "bounds_search",
"attributes": {
"bounds": {
"north": 40.7829,
"south": 40.7480,
"east": -73.9654,
"west": -73.9900
},
"category": "retail",
"limit": 100
}
}
}
Search Within Polygon
Find locations within a custom polygon.
POST /v1/geolocation/locations/within-polygon
Request Body:
{
"data": {
"type": "polygon_search",
"attributes": {
"polygon": [
{ "latitude": 40.7829, "longitude": -73.9654 },
{ "latitude": 40.7680, "longitude": -73.9500 },
{ "latitude": 40.7480, "longitude": -73.9654 },
{ "latitude": 40.7580, "longitude": -73.9900 },
{ "latitude": 40.7829, "longitude": -73.9654 }
],
"category": "office",
"include_distance_to_center": true
}
}
}
Geofences API
Define virtual geographic boundaries and monitor entry/exit events.
Create Geofence
Create a circular or polygon geofence.
POST /v1/geolocation/geofences
Request Body (Circle):
{
"data": {
"type": "geofences",
"attributes": {
"name": "Downtown Delivery Zone",
"description": "Free delivery area for downtown",
"type": "circle",
"center": {
"latitude": 40.7580,
"longitude": -73.9855
},
"radius": 3000,
"radius_unit": "meters",
"triggers": ["enter", "exit", "dwell"],
"dwell_time": 600,
"webhook_url": "https://api.yourapp.com/webhooks/geofence",
"metadata": {
"delivery_fee": 0,
"zone_type": "premium"
},
"schedule": {
"enabled": true,
"timezone": "America/New_York",
"windows": [
{ "day": "monday", "start": "08:00", "end": "22:00" },
{ "day": "tuesday", "start": "08:00", "end": "22:00" },
{ "day": "wednesday", "start": "08:00", "end": "22:00" },
{ "day": "thursday", "start": "08:00", "end": "22:00" },
{ "day": "friday", "start": "08:00", "end": "23:00" },
{ "day": "saturday", "start": "09:00", "end": "23:00" },
{ "day": "sunday", "start": "10:00", "end": "21:00" }
]
},
"status": "active"
}
}
}
Request Body (Polygon):
{
"data": {
"type": "geofences",
"attributes": {
"name": "Service Area - Manhattan",
"type": "polygon",
"vertices": [
{ "latitude": 40.8296, "longitude": -73.9262 },
{ "latitude": 40.8005, "longitude": -73.9291 },
{ "latitude": 40.7527, "longitude": -73.9772 },
{ "latitude": 40.7061, "longitude": -74.0183 },
{ "latitude": 40.7004, "longitude": -74.0412 },
{ "latitude": 40.7527, "longitude": -74.0087 },
{ "latitude": 40.8296, "longitude": -73.9262 }
],
"triggers": ["enter", "exit"],
"status": "active"
}
}
}
Response:
{
"data": {
"id": "gf_abc123",
"type": "geofences",
"attributes": {
"name": "Downtown Delivery Zone",
"description": "Free delivery area for downtown",
"type": "circle",
"center": {
"latitude": 40.7580,
"longitude": -73.9855
},
"radius": 3000,
"radius_unit": "meters",
"area_sq_meters": 28274333.88,
"triggers": ["enter", "exit", "dwell"],
"dwell_time": 600,
"webhook_url": "https://api.yourapp.com/webhooks/geofence",
"metadata": {
"delivery_fee": 0,
"zone_type": "premium"
},
"schedule": {
"enabled": true,
"timezone": "America/New_York",
"windows": [...]
},
"status": "active",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
}
}
List Geofences
GET /v1/geolocation/geofences
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
type | string | Filter by type: circle, polygon |
status | string | Filter by status: active, inactive |
search | string | Search in name and description |
Get Geofence
GET /v1/geolocation/geofences/:id
Update Geofence
PATCH /v1/geolocation/geofences/:id
Delete Geofence
DELETE /v1/geolocation/geofences/:id
Check Point in Geofence
Check if a point is inside a specific geofence.
POST /v1/geolocation/geofences/:id/contains
Request Body:
{
"data": {
"type": "point_check",
"attributes": {
"latitude": 40.7580,
"longitude": -73.9855
}
}
}
Response:
{
"data": {
"type": "point_check_result",
"attributes": {
"geofence_id": "gf_abc123",
"geofence_name": "Downtown Delivery Zone",
"point": {
"latitude": 40.7580,
"longitude": -73.9855
},
"is_inside": true,
"distance_to_boundary": 1245.6,
"distance_unit": "meters",
"schedule_active": true
}
}
}
Find Containing Geofences
Find all geofences that contain a specific point.
POST /v1/geolocation/geofences/containing
Request Body:
{
"data": {
"type": "point_check",
"attributes": {
"latitude": 40.7580,
"longitude": -73.9855,
"include_inactive": false
}
}
}
Response:
{
"data": [
{
"id": "gf_abc123",
"type": "geofences",
"attributes": {
"name": "Downtown Delivery Zone",
"type": "circle",
"metadata": {
"delivery_fee": 0
}
}
},
{
"id": "gf_def456",
"type": "geofences",
"attributes": {
"name": "Manhattan Service Area",
"type": "polygon",
"metadata": {
"service_tier": "premium"
}
}
}
],
"meta": {
"total_count": 2
}
}
Geocoding API
Convert between addresses and geographic coordinates.
Forward Geocode
Convert an address to coordinates.
POST /v1/geolocation/geocode
Request Body:
{
"data": {
"type": "geocode_request",
"attributes": {
"address": "768 5th Avenue, New York, NY 10019",
"country": "US",
"language": "en",
"limit": 5
}
}
}
Response:
{
"data": [
{
"type": "geocode_result",
"attributes": {
"formatted_address": "768 5th Avenue, New York, NY 10019, USA",
"latitude": 40.7637,
"longitude": -73.9718,
"accuracy": "rooftop",
"confidence": 0.98,
"components": {
"street_number": "768",
"street_name": "5th Avenue",
"city": "New York",
"county": "New York County",
"state": "New York",
"state_code": "NY",
"postal_code": "10019",
"country": "United States",
"country_code": "US"
},
"bounds": {
"north": 40.7647,
"south": 40.7627,
"east": -73.9708,
"west": -73.9728
},
"place_id": "ChIJ...",
"provider": "google"
}
}
],
"meta": {
"total_results": 1,
"query": "768 5th Avenue, New York, NY 10019"
}
}
Reverse Geocode
Convert coordinates to an address.
POST /v1/geolocation/reverse-geocode
Request Body:
{
"data": {
"type": "reverse_geocode_request",
"attributes": {
"latitude": 40.7637,
"longitude": -73.9718,
"language": "en",
"result_types": ["street_address", "premise"]
}
}
}
Response:
{
"data": {
"type": "reverse_geocode_result",
"attributes": {
"formatted_address": "768 5th Avenue, New York, NY 10019, USA",
"latitude": 40.7637,
"longitude": -73.9718,
"components": {
"street_number": "768",
"street_name": "5th Avenue",
"neighborhood": "Midtown Manhattan",
"city": "New York",
"county": "New York County",
"state": "New York",
"state_code": "NY",
"postal_code": "10019",
"country": "United States",
"country_code": "US"
},
"place_id": "ChIJ...",
"provider": "google"
}
}
}
Batch Geocode
Geocode multiple addresses in a single request.
POST /v1/geolocation/geocode/batch
Request Body:
{
"data": {
"type": "batch_geocode_request",
"attributes": {
"addresses": [
{ "id": "addr_1", "address": "768 5th Avenue, New York, NY" },
{ "id": "addr_2", "address": "1600 Pennsylvania Avenue NW, Washington, DC" },
{ "id": "addr_3", "address": "350 5th Avenue, New York, NY" }
],
"country": "US"
}
}
}
Response:
{
"data": [
{
"id": "addr_1",
"type": "geocode_result",
"attributes": {
"formatted_address": "768 5th Avenue, New York, NY 10019, USA",
"latitude": 40.7637,
"longitude": -73.9718,
"accuracy": "rooftop",
"status": "success"
}
},
{
"id": "addr_2",
"type": "geocode_result",
"attributes": {
"formatted_address": "1600 Pennsylvania Avenue NW, Washington, DC 20500, USA",
"latitude": 38.8977,
"longitude": -77.0365,
"accuracy": "rooftop",
"status": "success"
}
},
{
"id": "addr_3",
"type": "geocode_result",
"attributes": {
"formatted_address": "350 5th Avenue, New York, NY 10118, USA",
"latitude": 40.7484,
"longitude": -73.9857,
"accuracy": "rooftop",
"status": "success"
}
}
],
"meta": {
"total_requests": 3,
"successful": 3,
"failed": 0
}
}
Autocomplete Address
Get address suggestions as the user types.
GET /v1/geolocation/geocode/autocomplete
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
input | string | Partial address input |
country | string | Limit to country (ISO code) |
types | string | Address types: address, establishment, geocode |
location | string | Bias toward location lat,lng |
radius | integer | Bias radius in meters |
language | string | Language code (default: en) |
Example Request:
curl -X GET "https://api.23blocks.com/v1/geolocation/geocode/autocomplete?input=768%205th%20Ave&country=US" \
-H "X-App-Id: your-app-id" \
-H "X-Api-Key: your-api-key"
Response:
{
"data": [
{
"type": "autocomplete_suggestion",
"attributes": {
"description": "768 5th Avenue, New York, NY, USA",
"place_id": "ChIJ...",
"matched_substrings": [
{ "offset": 0, "length": 12 }
],
"structured": {
"main_text": "768 5th Avenue",
"secondary_text": "New York, NY, USA"
}
}
},
{
"type": "autocomplete_suggestion",
"attributes": {
"description": "768 5th Street, San Francisco, CA, USA",
"place_id": "ChIK...",
"matched_substrings": [
{ "offset": 0, "length": 7 }
]
}
}
],
"meta": {
"query": "768 5th Ave"
}
}
Distance API
Calculate distances and travel times between points.
Calculate Distance
Calculate distance between two points.
POST /v1/geolocation/distance
Request Body:
{
"data": {
"type": "distance_request",
"attributes": {
"origin": {
"latitude": 40.7128,
"longitude": -74.0060
},
"destination": {
"latitude": 40.7580,
"longitude": -73.9855
},
"formula": "haversine",
"unit": "kilometers"
}
}
}
Parameters:
| Parameter | Type | Description |
|---|---|---|
formula | string | Calculation formula: haversine, vincenty, spherical_law_of_cosines |
unit | string | Result unit: meters, kilometers, miles, feet, nautical_miles |
Response:
{
"data": {
"type": "distance_result",
"attributes": {
"origin": {
"latitude": 40.7128,
"longitude": -74.0060
},
"destination": {
"latitude": 40.7580,
"longitude": -73.9855
},
"distance": 5.234,
"unit": "kilometers",
"formula": "haversine",
"bearing": {
"initial": 25.4,
"final": 25.6
}
}
}
}
Distance Matrix
Calculate distances between multiple origins and destinations.
POST /v1/geolocation/distance/matrix
Request Body:
{
"data": {
"type": "distance_matrix_request",
"attributes": {
"origins": [
{ "id": "warehouse_1", "latitude": 40.7128, "longitude": -74.0060 },
{ "id": "warehouse_2", "latitude": 40.7580, "longitude": -73.9855 }
],
"destinations": [
{ "id": "customer_1", "latitude": 40.7829, "longitude": -73.9654 },
{ "id": "customer_2", "latitude": 40.7484, "longitude": -73.9857 },
{ "id": "customer_3", "latitude": 40.7061, "longitude": -74.0183 }
],
"mode": "driving",
"unit": "kilometers",
"include_duration": true,
"departure_time": "2024-01-15T09:00:00Z"
}
}
}
Response:
{
"data": {
"type": "distance_matrix_result",
"attributes": {
"rows": [
{
"origin_id": "warehouse_1",
"elements": [
{
"destination_id": "customer_1",
"distance": 8.45,
"duration": 1245,
"duration_in_traffic": 1520,
"status": "ok"
},
{
"destination_id": "customer_2",
"distance": 4.12,
"duration": 890,
"duration_in_traffic": 1050,
"status": "ok"
},
{
"destination_id": "customer_3",
"distance": 1.23,
"duration": 340,
"duration_in_traffic": 420,
"status": "ok"
}
]
},
{
"origin_id": "warehouse_2",
"elements": [
{
"destination_id": "customer_1",
"distance": 3.21,
"duration": 620,
"duration_in_traffic": 780,
"status": "ok"
},
{
"destination_id": "customer_2",
"distance": 1.15,
"duration": 240,
"duration_in_traffic": 290,
"status": "ok"
},
{
"destination_id": "customer_3",
"distance": 5.87,
"duration": 1100,
"duration_in_traffic": 1350,
"status": "ok"
}
]
}
],
"unit": "kilometers",
"duration_unit": "seconds",
"mode": "driving"
}
}
}
Routes API
Calculate routes and optimize multi-stop journeys.
Get Directions
Calculate route directions between points.
POST /v1/geolocation/routes/directions
Request Body:
{
"data": {
"type": "directions_request",
"attributes": {
"origin": {
"latitude": 40.7128,
"longitude": -74.0060
},
"destination": {
"latitude": 40.7580,
"longitude": -73.9855
},
"waypoints": [
{ "latitude": 40.7484, "longitude": -73.9857, "stopover": true }
],
"mode": "driving",
"alternatives": true,
"avoid": ["tolls", "highways"],
"departure_time": "2024-01-15T09:00:00Z",
"optimize_waypoints": false,
"units": "metric"
}
}
}
Parameters:
| Parameter | Type | Description |
|---|---|---|
mode | string | Travel mode: driving, walking, bicycling, transit |
alternatives | boolean | Return alternative routes |
avoid | array | Features to avoid: tolls, highways, ferries, indoor |
departure_time | string | Departure time for traffic estimation |
arrival_time | string | Desired arrival time (transit only) |
optimize_waypoints | boolean | Optimize waypoint order |
units | string | metric or imperial |
Response:
{
"data": {
"type": "directions_result",
"attributes": {
"routes": [
{
"summary": "Broadway",
"distance": {
"value": 6542,
"text": "6.5 km"
},
"duration": {
"value": 1234,
"text": "21 mins"
},
"duration_in_traffic": {
"value": 1520,
"text": "25 mins"
},
"polyline": "a~l~Fjk~uOwHJy@P...",
"bounds": {
"north": 40.7580,
"south": 40.7128,
"east": -73.9855,
"west": -74.0060
},
"legs": [
{
"start_location": { "latitude": 40.7128, "longitude": -74.0060 },
"end_location": { "latitude": 40.7484, "longitude": -73.9857 },
"distance": { "value": 4123, "text": "4.1 km" },
"duration": { "value": 782, "text": "13 mins" },
"steps": [
{
"instruction": "Head north on Broadway toward Chambers St",
"distance": { "value": 245, "text": "245 m" },
"duration": { "value": 45, "text": "1 min" },
"start_location": { "latitude": 40.7128, "longitude": -74.0060 },
"end_location": { "latitude": 40.7150, "longitude": -74.0055 },
"maneuver": "straight",
"polyline": "a~l~Fjk~uOwHJ"
}
]
}
],
"warnings": [],
"waypoint_order": [0]
}
],
"geocoded_waypoints": [
{
"status": "ok",
"place_id": "ChIJ...",
"types": ["street_address"]
}
]
}
}
}
Optimize Route
Find the optimal order for visiting multiple stops.
POST /v1/geolocation/routes/optimize
Request Body:
{
"data": {
"type": "route_optimization_request",
"attributes": {
"start": {
"id": "depot",
"latitude": 40.7128,
"longitude": -74.0060
},
"end": {
"id": "depot",
"latitude": 40.7128,
"longitude": -74.0060
},
"stops": [
{
"id": "delivery_1",
"latitude": 40.7829,
"longitude": -73.9654,
"service_time": 600,
"time_window": { "start": "09:00", "end": "12:00" },
"priority": 1
},
{
"id": "delivery_2",
"latitude": 40.7580,
"longitude": -73.9855,
"service_time": 300,
"time_window": { "start": "10:00", "end": "14:00" },
"priority": 2
},
{
"id": "delivery_3",
"latitude": 40.7484,
"longitude": -73.9857,
"service_time": 450,
"priority": 3
}
],
"mode": "driving",
"departure_time": "2024-01-15T08:00:00Z",
"optimization_target": "time",
"constraints": {
"max_route_duration": 28800,
"max_route_distance": 100000
}
}
}
}
Parameters:
| Parameter | Type | Description |
|---|---|---|
optimization_target | string | Optimize for: time, distance, balanced |
constraints.max_route_duration | integer | Max duration in seconds |
constraints.max_route_distance | integer | Max distance in meters |
Response:
{
"data": {
"type": "route_optimization_result",
"attributes": {
"optimized_route": {
"total_distance": 15234,
"total_duration": 3420,
"total_service_time": 1350,
"stops": [
{
"id": "depot",
"type": "start",
"arrival_time": "2024-01-15T08:00:00Z",
"departure_time": "2024-01-15T08:00:00Z"
},
{
"id": "delivery_3",
"order": 1,
"arrival_time": "2024-01-15T08:18:00Z",
"departure_time": "2024-01-15T08:25:30Z",
"wait_time": 0,
"distance_from_previous": 4123
},
{
"id": "delivery_2",
"order": 2,
"arrival_time": "2024-01-15T08:32:00Z",
"departure_time": "2024-01-15T08:37:00Z",
"wait_time": 0,
"distance_from_previous": 1456
},
{
"id": "delivery_1",
"order": 3,
"arrival_time": "2024-01-15T08:52:00Z",
"departure_time": "2024-01-15T09:02:00Z",
"wait_time": 0,
"distance_from_previous": 3211
},
{
"id": "depot",
"type": "end",
"arrival_time": "2024-01-15T09:17:00Z",
"distance_from_previous": 6444
}
],
"polyline": "encoded_polyline_string..."
},
"optimization_stats": {
"distance_saved": 8234,
"time_saved": 1245,
"improvement_percentage": 35.1
}
}
}
}
Areas API
Define and manage custom geographic regions.
Create Area
Create a custom area/region.
POST /v1/geolocation/areas
Request Body:
{
"data": {
"type": "areas",
"attributes": {
"name": "Manhattan",
"description": "Manhattan borough service area",
"type": "polygon",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[-74.0479, 40.6829],
[-73.9067, 40.7934],
[-73.9067, 40.8789],
[-73.9340, 40.8789],
[-74.0479, 40.6829]
]
]
},
"properties": {
"region_code": "NYC-MAN",
"service_level": "premium",
"delivery_fee": 0
},
"status": "active"
}
}
}
Response:
{
"data": {
"id": "area_abc123",
"type": "areas",
"attributes": {
"name": "Manhattan",
"description": "Manhattan borough service area",
"type": "polygon",
"geometry": {
"type": "Polygon",
"coordinates": [...]
},
"centroid": {
"latitude": 40.7831,
"longitude": -73.9712
},
"area_sq_km": 59.1,
"perimeter_km": 45.2,
"bounds": {
"north": 40.8789,
"south": 40.6829,
"east": -73.9067,
"west": -74.0479
},
"properties": {
"region_code": "NYC-MAN",
"service_level": "premium",
"delivery_fee": 0
},
"status": "active",
"created_at": "2024-01-15T10:30:00Z"
}
}
}
List Areas
GET /v1/geolocation/areas
Get Area
GET /v1/geolocation/areas/:id
Update Area
PATCH /v1/geolocation/areas/:id
Delete Area
DELETE /v1/geolocation/areas/:id
Check Point in Area
POST /v1/geolocation/areas/:id/contains
Request Body:
{
"data": {
"type": "point_check",
"attributes": {
"latitude": 40.7580,
"longitude": -73.9855
}
}
}
Get Area for Point
Find which area contains a point.
POST /v1/geolocation/areas/for-point
Request Body:
{
"data": {
"type": "point_lookup",
"attributes": {
"latitude": 40.7580,
"longitude": -73.9855
}
}
}
Tracking API
Track entities in real-time and receive geofence events.
Update Entity Location
Update the current location of a tracked entity.
POST /v1/geolocation/tracking/:entity_id/location
Request Body:
{
"data": {
"type": "location_update",
"attributes": {
"latitude": 40.7580,
"longitude": -73.9855,
"altitude": 10.5,
"accuracy": 15.0,
"heading": 45.2,
"speed": 12.5,
"timestamp": "2024-01-15T10:30:00Z",
"metadata": {
"battery_level": 85,
"activity_type": "driving"
}
}
}
}
Response:
{
"data": {
"type": "location_update_result",
"attributes": {
"entity_id": "entity_123",
"location": {
"latitude": 40.7580,
"longitude": -73.9855,
"timestamp": "2024-01-15T10:30:00Z"
},
"geofence_events": [
{
"geofence_id": "gf_abc123",
"geofence_name": "Downtown Zone",
"event_type": "enter",
"timestamp": "2024-01-15T10:30:00Z"
}
]
}
}
}
Get Entity Location History
GET /v1/geolocation/tracking/:entity_id/history
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
start_time | string | History start time (ISO 8601) |
end_time | string | History end time (ISO 8601) |
limit | integer | Max results (default: 100) |
Batch Location Update
Update locations for multiple entities.
POST /v1/geolocation/tracking/batch
Request Body:
{
"data": {
"type": "batch_location_update",
"attributes": {
"updates": [
{
"entity_id": "driver_1",
"latitude": 40.7580,
"longitude": -73.9855,
"timestamp": "2024-01-15T10:30:00Z"
},
{
"entity_id": "driver_2",
"latitude": 40.7484,
"longitude": -73.9857,
"timestamp": "2024-01-15T10:30:00Z"
}
]
}
}
}
Webhooks
Geolocation Block can send webhooks for real-time notifications.
Supported Events
| Event | Description |
|---|---|
geofence.enter | Entity entered a geofence |
geofence.exit | Entity exited a geofence |
geofence.dwell | Entity has been inside geofence for dwell_time |
location.created | New location was created |
location.updated | Location was updated |
location.deleted | Location was deleted |
area.created | New area was created |
area.updated | Area was updated |
tracking.started | Entity tracking started |
tracking.stopped | Entity tracking stopped |
Webhook Payload
{
"id": "evt_abc123",
"type": "geofence.enter",
"created_at": "2024-01-15T10:30:00Z",
"data": {
"entity_id": "driver_123",
"geofence": {
"id": "gf_abc123",
"name": "Downtown Zone",
"type": "circle"
},
"location": {
"latitude": 40.7580,
"longitude": -73.9855,
"timestamp": "2024-01-15T10:30:00Z"
},
"metadata": {
"entry_point": {
"latitude": 40.7570,
"longitude": -73.9850
},
"speed_at_entry": 25.5
}
}
}
Configure Webhooks
POST /v1/geolocation/webhooks
Request Body:
{
"data": {
"type": "webhooks",
"attributes": {
"url": "https://api.yourapp.com/webhooks/geolocation",
"events": ["geofence.enter", "geofence.exit", "geofence.dwell"],
"secret": "whsec_...",
"active": true,
"headers": {
"X-Custom-Header": "value"
}
}
}
}
Error Handling
Error Response Format
{
"errors": [
{
"status": "400",
"code": "invalid_coordinates",
"title": "Invalid Coordinates",
"detail": "Latitude must be between -90 and 90. Received: 95.5",
"source": {
"pointer": "/data/attributes/latitude"
}
}
]
}
Common Error Codes
| Status | Code | Description |
|---|---|---|
| 400 | invalid_coordinates | Invalid latitude or longitude values |
| 400 | invalid_radius | Radius must be positive |
| 400 | invalid_polygon | Polygon must have at least 3 vertices |
| 400 | polygon_not_closed | Polygon first and last points must match |
| 404 | location_not_found | Location with given ID not found |
| 404 | geofence_not_found | Geofence with given ID not found |
| 404 | area_not_found | Area with given ID not found |
| 422 | geocoding_failed | Unable to geocode the given address |
| 422 | route_not_found | No route found between points |
| 429 | rate_limit_exceeded | Too many requests |
| 500 | provider_error | External provider error |
SDK Examples
TypeScript/JavaScript
import { create23BlocksClient } from '@23blocks/sdk';
const client = create23BlocksClient({
urls: { geolocation: 'https://geolocation.api.us.23blocks.com' },
appId: process.env.APP_ID,
apiKey: process.env.API_KEY,
});
// Create a store location
async function createStoreLocation() {
const store = await client.geolocation.locations.create({
name: 'Downtown Store',
latitude: 40.7580,
longitude: -73.9855,
category: 'retail',
address: {
street: '123 Broadway',
city: 'New York',
state: 'NY',
postal_code: '10001',
country: 'US'
},
metadata: {
parking: true,
hours: '9am-9pm'
}
});
return store;
}
// Find stores near user
async function findNearbyStores(userLat: number, userLng: number) {
const stores = await client.geolocation.locations.nearby({
latitude: userLat,
longitude: userLng,
radius: 10000, // 10km
category: 'retail',
sort_by: 'distance',
limit: 10
});
return stores;
}
// Geocode an address
async function geocodeAddress(address: string) {
const results = await client.geolocation.geocode({
address,
country: 'US',
limit: 1
});
if (results.length > 0) {
return {
lat: results[0].latitude,
lng: results[0].longitude,
formatted: results[0].formatted_address
};
}
throw new Error('Address not found');
}
// Check if delivery address is in service area
async function isInDeliveryZone(lat: number, lng: number) {
const geofences = await client.geolocation.geofences.containing({
latitude: lat,
longitude: lng
});
const deliveryZone = geofences.find(g =>
g.metadata?.zone_type === 'delivery'
);
return deliveryZone !== null;
}
// Optimize delivery route
async function optimizeDeliveryRoute(stops: Array<{id: string, lat: number, lng: number}>) {
const optimized = await client.geolocation.routes.optimize({
start: {
id: 'warehouse',
latitude: 40.7128,
longitude: -74.0060
},
end: {
id: 'warehouse',
latitude: 40.7128,
longitude: -74.0060
},
stops: stops.map(s => ({
id: s.id,
latitude: s.lat,
longitude: s.lng,
service_time: 300 // 5 minutes per stop
})),
mode: 'driving',
optimization_target: 'time'
});
return optimized;
}
React Store Locator
import React, { useState, useEffect, useCallback } from 'react';
import { create23BlocksClient } from '@23blocks/sdk';
interface Store {
id: string;
name: string;
latitude: number;
longitude: number;
address: {
formatted: string;
};
distance?: number;
}
const client = create23BlocksClient({
urls: { geolocation: 'https://geolocation.api.us.23blocks.com' },
appId: process.env.REACT_APP_APP_ID!,
apiKey: process.env.REACT_APP_API_KEY!,
});
export function StoreLocator() {
const [stores, setStores] = useState<Store[]>([]);
const [userLocation, setUserLocation] = useState<{lat: number, lng: number} | null>(null);
const [searchAddress, setSearchAddress] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Get user's current location
const getCurrentLocation = useCallback(() => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
setUserLocation({
lat: position.coords.latitude,
lng: position.coords.longitude
});
},
(error) => {
setError('Unable to get your location');
}
);
}
}, []);
// Search stores near location
const searchNearby = useCallback(async (lat: number, lng: number) => {
setLoading(true);
setError(null);
try {
const results = await client.geolocation.locations.nearby({
latitude: lat,
longitude: lng,
radius: 25000, // 25km
category: 'retail',
include_distance: true,
sort_by: 'distance',
limit: 20
});
setStores(results);
} catch (err) {
setError('Failed to find nearby stores');
} finally {
setLoading(false);
}
}, []);
// Geocode address and search
const searchByAddress = async () => {
if (!searchAddress) return;
setLoading(true);
setError(null);
try {
const geocoded = await client.geolocation.geocode({
address: searchAddress,
country: 'US',
limit: 1
});
if (geocoded.length > 0) {
const { latitude, longitude } = geocoded[0];
setUserLocation({ lat: latitude, lng: longitude });
await searchNearby(latitude, longitude);
} else {
setError('Address not found');
}
} catch (err) {
setError('Failed to geocode address');
} finally {
setLoading(false);
}
};
// Search when user location changes
useEffect(() => {
if (userLocation) {
searchNearby(userLocation.lat, userLocation.lng);
}
}, [userLocation, searchNearby]);
// Format distance for display
const formatDistance = (meters: number) => {
if (meters < 1000) {
return `${Math.round(meters)}m`;
}
return `${(meters / 1000).toFixed(1)}km`;
};
return (
<div className="store-locator">
<div className="search-bar">
<input
type="text"
placeholder="Enter address or zip code"
value={searchAddress}
onChange={(e) => setSearchAddress(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && searchByAddress()}
/>
<button onClick={searchByAddress}>Search</button>
<button onClick={getCurrentLocation}>Use My Location</button>
</div>
{error && <div className="error">{error}</div>}
{loading ? (
<div className="loading">Finding stores...</div>
) : (
<div className="store-list">
{stores.map(store => (
<div key={store.id} className="store-card">
<h3>{store.name}</h3>
<p>{store.address.formatted}</p>
{store.distance && (
<span className="distance">
{formatDistance(store.distance)}
</span>
)}
<button onClick={() => {
window.open(
`https://maps.google.com/?daddr=${store.latitude},${store.longitude}`,
'_blank'
);
}}>
Get Directions
</button>
</div>
))}
</div>
)}
</div>
);
}
React Native Geofence Monitor
import React, { useEffect, useState } from 'react';
import { View, Text, Alert } from 'react-native';
import * as Location from 'expo-location';
import { create23BlocksClient } from '@23blocks/sdk';
const client = create23BlocksClient({
urls: { geolocation: 'https://geolocation.api.us.23blocks.com' },
appId: 'your-app-id',
apiKey: 'your-api-key',
});
interface GeofenceEvent {
geofence_id: string;
geofence_name: string;
event_type: 'enter' | 'exit' | 'dwell';
timestamp: string;
}
export function DeliveryTracker({ driverId }: { driverId: string }) {
const [currentLocation, setCurrentLocation] = useState<Location.LocationObject | null>(null);
const [events, setEvents] = useState<GeofenceEvent[]>([]);
const [tracking, setTracking] = useState(false);
useEffect(() => {
let subscription: Location.LocationSubscription | null = null;
const startTracking = async () => {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
Alert.alert('Permission denied', 'Location permission is required');
return;
}
setTracking(true);
subscription = await Location.watchPositionAsync(
{
accuracy: Location.Accuracy.High,
distanceInterval: 10, // Update every 10 meters
timeInterval: 5000 // Or every 5 seconds
},
async (location) => {
setCurrentLocation(location);
// Update location on server
try {
const result = await client.geolocation.tracking.updateLocation(driverId, {
latitude: location.coords.latitude,
longitude: location.coords.longitude,
altitude: location.coords.altitude || undefined,
accuracy: location.coords.accuracy || undefined,
heading: location.coords.heading || undefined,
speed: location.coords.speed || undefined,
timestamp: new Date(location.timestamp).toISOString()
});
// Handle geofence events
if (result.geofence_events && result.geofence_events.length > 0) {
setEvents(prev => [...result.geofence_events, ...prev]);
result.geofence_events.forEach(event => {
if (event.event_type === 'enter') {
Alert.alert(
'Arrived',
`You have entered ${event.geofence_name}`
);
}
});
}
} catch (error) {
console.error('Failed to update location:', error);
}
}
);
};
startTracking();
return () => {
if (subscription) {
subscription.remove();
}
setTracking(false);
};
}, [driverId]);
return (
<View style={{ flex: 1, padding: 20 }}>
<Text style={{ fontSize: 18, fontWeight: 'bold' }}>
Delivery Tracking
</Text>
<View style={{ marginTop: 20 }}>
<Text>Status: {tracking ? 'Active' : 'Inactive'}</Text>
{currentLocation && (
<>
<Text>Lat: {currentLocation.coords.latitude.toFixed(6)}</Text>
<Text>Lng: {currentLocation.coords.longitude.toFixed(6)}</Text>
{currentLocation.coords.speed && (
<Text>Speed: {(currentLocation.coords.speed * 3.6).toFixed(1)} km/h</Text>
)}
</>
)}
</View>
<View style={{ marginTop: 20 }}>
<Text style={{ fontSize: 16, fontWeight: 'bold' }}>
Recent Events
</Text>
{events.slice(0, 5).map((event, index) => (
<View key={index} style={{ marginTop: 10, padding: 10, backgroundColor: '#f0f0f0' }}>
<Text>{event.geofence_name}</Text>
<Text style={{ color: event.event_type === 'enter' ? 'green' : 'red' }}>
{event.event_type.toUpperCase()}
</Text>
<Text style={{ fontSize: 12, color: '#666' }}>
{new Date(event.timestamp).toLocaleTimeString()}
</Text>
</View>
))}
</View>
</View>
);
}
Rate Limits
| Plan | Locations | Geocoding | Distance | Routes |
|---|---|---|---|---|
| Free | 1,000/day | 500/day | 500/day | 100/day |
| Starter | 10,000/day | 5,000/day | 5,000/day | 1,000/day |
| Pro | 100,000/day | 50,000/day | 50,000/day | 10,000/day |
| Enterprise | Unlimited | Custom | Custom | Custom |
Best Practices
Optimize Location Queries
// Use geohash-based queries for better performance
const stores = await client.geolocation.locations.nearby({
latitude: userLat,
longitude: userLng,
radius: 5000,
// Only fetch fields you need
fields: ['id', 'name', 'latitude', 'longitude', 'address.formatted'],
// Use reasonable limits
limit: 20
});
Cache Geocoding Results
// Cache geocoded addresses to reduce API calls
const geocodeCache = new Map<string, GeocodedResult>();
async function getCachedGeocode(address: string) {
const cacheKey = address.toLowerCase().trim();
if (geocodeCache.has(cacheKey)) {
return geocodeCache.get(cacheKey)!;
}
const result = await client.geolocation.geocode({ address });
if (result.length > 0) {
geocodeCache.set(cacheKey, result[0]);
return result[0];
}
throw new Error('Address not found');
}
Handle Geofence Events Efficiently
// Use dwell time to avoid rapid enter/exit events
const geofence = await client.geolocation.geofences.create({
name: 'Store Zone',
type: 'circle',
center: { latitude: 40.7580, longitude: -73.9855 },
radius: 100,
triggers: ['enter', 'exit', 'dwell'],
dwell_time: 60 // Wait 60 seconds before triggering dwell
});
Batch Operations
// Use batch geocoding for multiple addresses
const addresses = [
'123 Main St, New York, NY',
'456 Oak Ave, Los Angeles, CA',
'789 Pine Rd, Chicago, IL'
];
const results = await client.geolocation.geocode.batch({
addresses: addresses.map((addr, i) => ({
id: `addr_${i}`,
address: addr
}))
});
Changelog
v2.1.0
- Added route optimization with time windows
- Improved polygon geofence performance
- Added batch location updates
v2.0.0
- Complete API redesign following JSON:API spec
- Added area management
- Added real-time tracking with geofence events
- Added address autocomplete
v1.0.0
- Initial release with locations, geofences, geocoding, and distance APIs
Use This Block with AI Agents
This block ships an official Claude Code plugin from the public 23blocks marketplace (19 plugins, 205 skills). Your agent gets full working knowledge of this API — endpoints, identity registration, and conventions.
# one-time: add the 23blocks marketplace
claude plugin marketplace add https://github.com/23blocks-OS/ai-agents
# install this block's plugin
claude plugin install geolocation-block
Configure BLOCKS_API_URL, BLOCKS_API_KEY, and BLOCKS_AUTH_TOKEN. Register identity first: each block is autonomous — register the user with this block's identity endpoint before private calls (only user_unique_id is required; email/phone are optional notification-routing fields).
Full reference: AI Agents & Claude Code Plugins · github.com/23blocks-OS/ai-agents