Skip to main content

Geolocation Block

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

FeatureDescription
Location StorageStore and manage points of interest with rich metadata
GeofencingDefine virtual boundaries and trigger events on entry/exit
GeocodingConvert addresses to coordinates and vice versa
Reverse GeocodingGet addresses from coordinates
Nearby SearchFind locations within a radius or bounding box
Distance CalculationsCalculate distances with multiple formulas
Route OptimizationFind optimal paths between multiple waypoints
Area DefinitionsDefine complex polygons and service zones
Real-time TrackingTrack moving entities with geofence triggers

API Endpoint

ServiceURL
Geolocationhttps://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:

ParameterTypeDescription
categorystringFilter by category
subcategorystringFilter by subcategory
tagsstringComma-separated list of tags (any match)
statusstringFilter by status: active, inactive, pending
countrystringFilter by country code (ISO 3166-1 alpha-2)
statestringFilter by state/province
citystringFilter by city
searchstringFull-text search in name and description
page[number]integerPage number (default: 1)
page[size]integerItems per page (default: 20, max: 100)
sortstringSort 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:

ParameterTypeDescription
permanentbooleanPermanently 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:

ParameterTypeDescription
latitudenumberCenter point latitude (-90 to 90)
longitudenumberCenter point longitude (-180 to 180)
radiusnumberSearch radius
unitstringDistance unit: meters, kilometers, miles, feet
categorystringFilter by category
tagsarrayFilter by tags (any match)
filtersobjectCustom metadata filters with operators
include_distancebooleanInclude distance in response
sort_bystringSort by: distance, name, rating
limitintegerMaximum 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:

ParameterTypeDescription
typestringFilter by type: circle, polygon
statusstringFilter by status: active, inactive
searchstringSearch 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:

ParameterTypeDescription
inputstringPartial address input
countrystringLimit to country (ISO code)
typesstringAddress types: address, establishment, geocode
locationstringBias toward location lat,lng
radiusintegerBias radius in meters
languagestringLanguage 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:

ParameterTypeDescription
formulastringCalculation formula: haversine, vincenty, spherical_law_of_cosines
unitstringResult 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:

ParameterTypeDescription
modestringTravel mode: driving, walking, bicycling, transit
alternativesbooleanReturn alternative routes
avoidarrayFeatures to avoid: tolls, highways, ferries, indoor
departure_timestringDeparture time for traffic estimation
arrival_timestringDesired arrival time (transit only)
optimize_waypointsbooleanOptimize waypoint order
unitsstringmetric 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:

ParameterTypeDescription
optimization_targetstringOptimize for: time, distance, balanced
constraints.max_route_durationintegerMax duration in seconds
constraints.max_route_distanceintegerMax 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:

ParameterTypeDescription
start_timestringHistory start time (ISO 8601)
end_timestringHistory end time (ISO 8601)
limitintegerMax 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

EventDescription
geofence.enterEntity entered a geofence
geofence.exitEntity exited a geofence
geofence.dwellEntity has been inside geofence for dwell_time
location.createdNew location was created
location.updatedLocation was updated
location.deletedLocation was deleted
area.createdNew area was created
area.updatedArea was updated
tracking.startedEntity tracking started
tracking.stoppedEntity 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

StatusCodeDescription
400invalid_coordinatesInvalid latitude or longitude values
400invalid_radiusRadius must be positive
400invalid_polygonPolygon must have at least 3 vertices
400polygon_not_closedPolygon first and last points must match
404location_not_foundLocation with given ID not found
404geofence_not_foundGeofence with given ID not found
404area_not_foundArea with given ID not found
422geocoding_failedUnable to geocode the given address
422route_not_foundNo route found between points
429rate_limit_exceededToo many requests
500provider_errorExternal 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

PlanLocationsGeocodingDistanceRoutes
Free1,000/day500/day500/day100/day
Starter10,000/day5,000/day5,000/day1,000/day
Pro100,000/day50,000/day50,000/day10,000/day
EnterpriseUnlimitedCustomCustomCustom

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