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: Use the same URL for all environments. Your API key determines the environment:
pk_test_*/sk_test_*→ Routes to Stagingpk_live_*/sk_live_*→ Routes to Production
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', // Use pk_test_* for staging, pk_live_* for production
});
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