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: Use the same URL for all environments. Your API key determines the environment:

  • pk_test_* / sk_test_* → Routes to Staging
  • pk_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:

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