Skip to main content

Geocoding

Resolve addresses to coordinates (forward), coordinates to addresses (reverse), suggest street names (autocomplete), and check your remaining balance (quota).

Two ways to geocode

This API resolves addresses through two complementary engines, and you can pick which one to use:

  • Nominatim (free text / reverse)forward with q free text and reverse (coordinate → address) run against OpenStreetMap data. Best for worldwide coverage and free-form input.
  • Internal, with our own maps (structured modes)internal resolves only against LogicSat's own street base, with explicit structured modes: street + corner, street + number, and route + km. Best when you already have the address split into fields and want the most accurate match on covered countries (Uruguay, Argentina, and more).

forward with structured fields (street / number / corner) also tries the internal engine first as part of its provider chain. Use the dedicated internal endpoint when you want to force a specific structured mode against our maps and skip the external providers entirely.

Paid service — this is the only endpoint billed per use

The geocoding service has an additional cost per use. Every successful forward or reverse lookup consumes one credit from your contracted geocoding allowance with LogicSat. It is not included in the free use of the rest of the API.

  • Costs a credit: forward and reverse lookups that return at least one result (1 credit per result that resolves).
  • Free: autocomplete, the quota balance check, lookups that return no results, and repeated lookups served from the short cache.
  • Check your balance any time with GET /apidev/v1/geocoding/quota — it never costs a credit.
  • The allowance is contracted with LogicSat. When it runs out, paid lookups return HTTP 402 until the balance is renewed or extended.
Prerequisites

All endpoints require a valid JWT token, API key, and tenant header. See Authentication. Every geocoding endpoint requires the APICLI_GEOCODE permission.

How billing works

Each paid response includes a quota block under meta so you always know where you stand after the call:

FieldTypeDescription
contractednumberTotal credits in the active period.
usednumberCredits already consumed in the active period.
remainingnumberCredits left (contractedused).
period_startstring | nullStart of the active billing window. null means no fixed start.
period_endstring | nullEnd of the active billing window. null means no expiry.
chargednumberCredits this single call consumed (0 for cache hits and empty results).
Plan ahead with quota

Before running a large batch, call GET /apidev/v1/geocoding/quota to see your remaining balance. If a batch runs out mid-way, it resolves as many items as the balance allows and returns a per-item error for the rest — see Forward Geocoding (batch).


Forward Geocoding

Turn an address into a coordinate. Send a structured address (country / department / street / number / corner, or route + km) or free text. Returns ranked candidates with precision, confidence, and normalized address components.

POST/apidev/v1/geocoding/forward
PermissionAPICLI_GEOCODE
Rate Limit30 req/min (sliding window)
Cost1 credit per result (free on cache hit / no results)

Request Body

Send a single query object, or a batch with { "items": [ ... ] } (1–25 queries). See Batch mode.

FieldTypeRequiredDescription
modestringNoauto (default), street_number, intersection, route_km, or freetext. auto infers the strategy from the fields you send.
countrystringConditionalCountry (ID or name). Narrows the search. Required except for freetext and route_km.
departmentstringConditionalDepartment / province (ID or name). Disambiguates same-named streets. Required except for freetext.
citystringNoCity / locality (ID or name). Biases the search.
streetstringConditionalStreet name. Accepts a full line like "Av. Brasil 2950 esq. Ellauri" — the engine splits it.
numberstringNoDoor / house number. Triggers street_number mode.
cornerstringNoCross street. Triggers intersection mode.
routestringConditionalRoute number. Combined with km, triggers route_km mode.
kmnumberConditionalKilometer / milestone marker.
qstringConditionalFree text. Triggers freetext mode.
providersstring[]NoSubset of internal, nominatim, google, mapbox, bing. Intersected with the providers enabled for your company; order is preserved.
limitnumberNoMax candidates to return. Min 1, Max 25, default 10.
langstringNoResult language (es, en, …).
bias_bboxnumber[4]No[minLng, minLat, maxLng, maxLat] bounding box to bias the search toward an area.

Response Fields

data.results is an array of candidates. Each candidate has:

FieldTypeDescription
latstringLatitude (decimal as string).
lngstringLongitude (decimal as string).
precisionstringResult quality: explicit, rooftop, range, intersection, route-km, street, approx, or centroid.
confidencenumber | nullProvider confidence from 0 to 1.
sourcestringProvider that resolved it: internal, nominatim, google, mapbox, or bing.
addressobjectNormalized components: street, number, corner, neighborhood, city, department, country, postcode.
formattedstringHuman-readable single-line address.
bboxnumber[4]Bounding box [minLng, minLat, maxLng, maxLat]. Present when available.
normalizedobjectPresent only when the street was corrected: { street, resolved_from } (the street used vs. the text you sent).

The meta block carries providers_tried (string[]), resolved_by (string | null), cached (boolean), and the quota block described in How billing works.

Code Example

curl -s -X POST "https://$TENANT/apidev/v1/geocoding/forward" \
-H "Authorization: Bearer $TOKEN" \
-H "X-API-Key: $APIKEY" \
-H "tenant: $TENANT" \
-H "Content-Type: application/json" \
-d '{
"country": "Uruguay",
"department": "Montevideo",
"street": "Av. Brasil",
"number": "2950"
}'

Example Response

{
"success": true,
"data": {
"results": [
{
"lat": "-34.90568300",
"lng": "-56.18820200",
"precision": "range",
"confidence": 0.82,
"source": "internal",
"address": {
"street": "Avenida Brasil",
"number": "2950",
"corner": "Ellauri",
"neighborhood": "Pocitos",
"city": "Montevideo",
"department": "Montevideo",
"country": "Uruguay",
"postcode": "11300"
},
"formatted": "Avenida Brasil 2950 esq. Ellauri, Pocitos, Montevideo",
"bbox": [-56.190, -34.907, -56.186, -34.904],
"normalized": {
"street": "Avenida Brasil",
"resolved_from": "Av. Brasil"
}
}
]
},
"meta": {
"providers_tried": ["internal"],
"resolved_by": "internal",
"cached": false,
"quota": {
"contracted": 5000,
"used": 1242,
"remaining": 3758,
"period_start": "2026-01-01T00:00:00",
"period_end": "2026-12-31T23:59:59",
"active": true,
"charged": 1
}
}
}
No results never costs a credit

A valid search that finds nothing returns 200 with "results": [] and "charged": 0. You are only billed when a lookup actually resolves to a location.

Batch mode

Send up to 25 queries at once with { "items": [ ... ] }. The response shape changes: data.items is an array of { index, success, results?, error? }, and meta.quota.charged reports the total credits consumed across the batch.

If the balance runs out partway through, the items resolved up to that point are billed and returned; each remaining item comes back with success: false and a GEOCODING_QUOTA_EXCEEDED error. The request still returns 200.

{
"success": true,
"data": {
"items": [
{
"index": 0,
"success": true,
"results": [
{
"lat": "-34.90568300",
"lng": "-56.18820200",
"precision": "range",
"confidence": 0.82,
"source": "internal",
"address": { "street": "Avenida Brasil", "number": "2950", "city": "Montevideo", "department": "Montevideo", "country": "Uruguay" },
"formatted": "Avenida Brasil 2950, Montevideo"
}
]
},
{
"index": 1,
"success": false,
"error": {
"code": "GEOCODING_QUOTA_EXCEEDED",
"message": "Geocoding quota exceeded"
}
}
]
},
"meta": {
"cached": false,
"quota": {
"contracted": 5000,
"used": 5000,
"remaining": 0,
"period_start": "2026-01-01T00:00:00",
"period_end": "2026-12-31T23:59:59",
"active": true,
"charged": 1
}
}
}

Reverse Geocoding

Turn a coordinate into an address. Same billing as forward — 1 credit per result, free on cache hit or empty result.

POST/apidev/v1/geocoding/reverse
PermissionAPICLI_GEOCODE
Rate Limit30 req/min (sliding window)
Cost1 credit per result (free on cache hit / no results)

Request Body

Send a single query object, or a batch with { "items": [ ... ] } (1–25 queries, same partial-billing rules as forward).

FieldTypeRequiredDescription
latstringYesLatitude (decimal as string).
lngstringYesLongitude (decimal as string).
langstringNoResult language (es, en, …).
zoomnumberNoLevel of detail. Min 3, Max 18.

Response Fields

Same candidate shape as Forward Geocoding. meta.providers_tried is ["nominatim"], and meta.quota carries the balance after the charge.

Code Example

curl -s -X POST "https://$TENANT/apidev/v1/geocoding/reverse" \
-H "Authorization: Bearer $TOKEN" \
-H "X-API-Key: $APIKEY" \
-H "tenant: $TENANT" \
-H "Content-Type: application/json" \
-d '{
"lat": "-34.90568300",
"lng": "-56.18820200"
}'

Example Response

{
"success": true,
"data": {
"results": [
{
"lat": "-34.90568300",
"lng": "-56.18820200",
"precision": "rooftop",
"confidence": 0.45,
"source": "nominatim",
"address": {
"street": "Avenida Brasil",
"number": "2950",
"neighborhood": "Pocitos",
"city": "Montevideo",
"department": "Montevideo",
"country": "Uruguay",
"postcode": "11300"
},
"formatted": "Avenida Brasil 2950, Pocitos, Montevideo, Uruguay",
"bbox": [-56.189, -34.906, -56.187, -34.905]
}
]
},
"meta": {
"providers_tried": ["nominatim"],
"resolved_by": "nominatim",
"cached": false,
"quota": {
"contracted": 5000,
"used": 1243,
"remaining": 3757,
"period_start": "2026-01-01T00:00:00",
"period_end": "2026-12-31T23:59:59",
"active": true,
"charged": 1
}
}
}

Internal Geocoding (our maps)

Resolve an address only against LogicSat's own street base — no Nominatim, no external providers. You choose an explicit structured mode, so there is no guessing: each mode maps directly to a search against our maps.

Use this when you have the address already split into fields and want the most precise match on covered countries, or when you specifically want to avoid the external providers.

modeResolvesRequired fieldsCoverage
street_cornerStreet and its cross street (intersection)country, street, cornerUruguay, Argentina, Ecuador, Chile, Paraguay, Colombia
street_numberStreet and door numbercountry, street, numberUruguay and Argentina (the countries with door-number ranges)
routeRoute and kilometer markerroute, kmUruguay
POST/apidev/v1/geocoding/internal
PermissionAPICLI_GEOCODE
Rate Limit30 req/min (sliding window)
Cost1 credit per result (free on cache hit / no results)
Same billing as forward / reverse

Internal geocoding is billed exactly like forward and reverse: 1 credit per result that resolves, free on cache hits and empty results. Only autocomplete and quota are always free.

Request Body

FieldTypeRequiredDescription
modestringYesstreet_corner, street_number, or route. Selects the structured search.
countrystringConditionalCountry (ID or name). Required for street_corner and street_number. Ignored for route (Uruguay only).
departmentstringNoDepartment / province (ID or name). Disambiguates same-named streets.
streetstringConditionalStreet name. Required for street_corner and street_number.
cornerstringConditionalCross street. Required for street_corner.
numberstringConditionalDoor / house number. Required for street_number.
routestringConditionalRoute number. Required for route.
kmstringConditionalKilometer / milestone marker. Required for route.

Response Fields

Same candidate shape as Forward Geocoding. Every result has source: "internal" and meta.providers_tried is ["internal"]. precision is intersection for street_corner, range for street_number (the engine returns the centroid of the block range, not the interpolated door), and route-km for route. meta.quota carries the balance after the charge.

Code Example

curl -s -X POST "https://$TENANT/apidev/v1/geocoding/internal" \
-H "Authorization: Bearer $TOKEN" \
-H "X-API-Key: $APIKEY" \
-H "tenant: $TENANT" \
-H "Content-Type: application/json" \
-d '{
"mode": "street_corner",
"country": "Uruguay",
"department": "Montevideo",
"street": "Av. Brasil",
"corner": "Ellauri"
}'

Example Response

{
"success": true,
"data": {
"results": [
{
"lat": "-34.90568300",
"lng": "-56.18820200",
"precision": "intersection",
"confidence": null,
"source": "internal",
"address": {
"street": "Avenida Brasil",
"corner": "Ellauri",
"neighborhood": "Pocitos",
"city": "Montevideo",
"department": "Montevideo",
"country": "Uruguay"
},
"formatted": "Avenida Brasil esq. Ellauri"
}
]
},
"meta": {
"providers_tried": ["internal"],
"resolved_by": "internal",
"cached": false,
"quota": {
"contracted": 5000,
"used": 1244,
"remaining": 3756,
"period_start": "2026-01-01T00:00:00",
"period_end": "2026-12-31T23:59:59",
"active": true,
"charged": 1
}
}
}

Autocomplete

Typeahead suggestions for a street name. Free — it returns no coordinates and never consumes a credit.

POST/apidev/v1/geocoding/autocomplete
PermissionAPICLI_GEOCODE
Rate Limit30 req/min (sliding window)
CostFree

Request Body

FieldTypeRequiredDescription
countrystringYesCountry (ID or name).
departmentstringNoDepartment / province (ID or name). Narrows the suggestions.
streetstringYesStreet fragment to match. Minimum 2 characters.
limitnumberNoMax suggestions. Min 1, Max 20, default 10.

Response Fields

data.suggestions is an array of { street, neighborhood?, city?, department? }. meta.cached indicates whether the result came from the short cache.

Code Example

curl -s -X POST "https://$TENANT/apidev/v1/geocoding/autocomplete" \
-H "Authorization: Bearer $TOKEN" \
-H "X-API-Key: $APIKEY" \
-H "tenant: $TENANT" \
-H "Content-Type: application/json" \
-d '{
"country": "Uruguay",
"department": "Montevideo",
"street": "bras"
}'

Example Response

{
"success": true,
"data": {
"suggestions": [
{
"street": "Avenida Brasil",
"neighborhood": "Pocitos",
"department": "Montevideo"
},
{
"street": "Brasilia",
"neighborhood": "Carrasco",
"department": "Montevideo"
}
]
},
"meta": {
"cached": false
}
}

Quota

Check your company's geocoding balance — contracted, used, remaining, and the active period. Free, and it never touches your allowance.

GET/apidev/v1/geocoding/quota
PermissionAPICLI_GEOCODE
Rate Limit30 req/min (sliding window)
CostFree

Response Fields

FieldTypeDescription
contractednumberTotal credits in the active period.
usednumberCredits consumed in the active period.
remainingnumberCredits left.
period_startstring | nullStart of the active billing window.
period_endstring | nullEnd of the active billing window.
activebooleantrue when there is a valid contracted window. false (with remaining: 0) when geocoding is not contracted or the window has lapsed.

Code Example

curl -s "https://$TENANT/apidev/v1/geocoding/quota" \
-H "Authorization: Bearer $TOKEN" \
-H "X-API-Key: $APIKEY" \
-H "tenant: $TENANT"

Example Response

{
"success": true,
"data": {
"contracted": 5000,
"used": 1243,
"remaining": 3757,
"period_start": "2026-01-01T00:00:00",
"period_end": "2026-12-31T23:59:59",
"active": true
},
"meta": {}
}
Timestamps

Period dates are returned without timezone (e.g., "2026-01-01T00:00:00"). The value represents the company's configured timezone. Do not append Z or apply UTC conversion — display as-is.


Errors

All endpoints on this page may return these errors. See Error Handling for the full reference.

CodeHTTPApplies toDescription
VALIDATION_ERROR400AllInvalid body fields (unknown field, wrong type, missing required field, batch over 25 items). Details in details[].
UNAUTHORIZED401AllMissing, invalid, or expired tenant / Authorization / X-API-Key.
FORBIDDEN403AllThe API key lacks the APICLI_GEOCODE permission.
GEOCODING_NOT_CONTRACTED402Forward, Reverse, InternalGeocoding is not contracted for your company, or the billing window has lapsed. Contract the geocoding service with LogicSat or renew the window.
GEOCODING_QUOTA_EXCEEDED402Forward, Reverse, InternalThe geocoding balance for the period is exhausted. Wait for renewal or extend your contracted allowance with LogicSat.
RATE_LIMITED429AllExceeded 30 req/min. This is a time-based throttle, separate from the per-use quota.
INTERNAL_ERROR500AllUnexpected server error. Retry; if it persists, contact support.
Payment required (402)

The two 402 codes carry the current balance directly under error.details so your integration can tell exactly why the call was refused. The allowance is contracted with LogicSat — top it up or renew the window to keep geocoding.

{
"success": false,
"error": {
"code": "GEOCODING_QUOTA_EXCEEDED",
"message": "Geocoding quota exceeded",
"details": {
"contracted": 5000,
"used": 5000,
"remaining": 0,
"period_start": "2026-01-01T00:00:00",
"period_end": "2026-12-31T23:59:59",
"active": true
}
}
}