Geocoding
Resuelve direcciones a coordenadas (forward), coordenadas a direcciones (reverse), sugiere nombres de calle (autocomplete) y consultá tu saldo restante (quota).
Esta API resuelve direcciones con dos motores complementarios, y vos elegís cuál usar:
- Nominatim (texto libre / reverse) —
forwardcon texto libreqyreverse(coordenada → dirección) trabajan sobre datos de OpenStreetMap. Ideal para cobertura mundial y entrada de texto libre. - Interno, con nuestros propios mapas (modos estructurados) —
internalresuelve solo contra la base de calles propia de LogicSat, con modos estructurados explícitos: calle + esquina, calle + número y ruta + km. Ideal cuando ya tenés la dirección separada en campos y querés la coincidencia más precisa en los países cubiertos (Uruguay, Argentina y más).
forward con campos estructurados (street / number / corner) también prueba el motor interno primero como parte de su cadena de proveedores. Usá el endpoint dedicado internal cuando quieras forzar un modo estructurado específico contra nuestros mapas y saltear por completo los proveedores externos.
El servicio de geocoding tiene costo adicional por uso. Cada búsqueda exitosa de forward o reverse descuenta un crédito de tu cupo de geocodificación contratado con LogicSat. No está incluido en el uso libre del resto de la API.
- Descuenta crédito: las búsquedas
forwardyreverseque devuelven al menos un resultado (1 crédito por resultado que resuelve). - Gratis:
autocomplete, la consulta de saldoquota, las búsquedas que no devuelven resultados y las búsquedas repetidas servidas desde la caché corta. - Consultá tu saldo cuando quieras con
GET /apidev/v1/geocoding/quota— nunca descuenta un crédito. - El cupo se contrata con LogicSat. Cuando se agota, las búsquedas que cobran devuelven HTTP 402 hasta que el saldo se renueva o se amplía.
Todos los endpoints requieren un token JWT válido, una clave de API y el encabezado tenant. Consultá Autenticación. Cada endpoint de geocoding requiere el permiso APICLI_GEOCODE.
Cómo funciona el cobro
Cada respuesta que cobra incluye un bloque quota dentro de meta para que siempre sepas cómo quedó tu saldo después de la llamada:
| Campo | Tipo | Descripción |
|---|---|---|
contracted | number | Créditos totales del período activo. |
used | number | Créditos ya consumidos en el período activo. |
remaining | number | Créditos restantes (contracted − used). |
period_start | string | null | Inicio de la ventana de facturación activa. null significa sin inicio fijo. |
period_end | string | null | Fin de la ventana de facturación activa. null significa sin vencimiento. |
charged | number | Créditos que descontó esta llamada (0 en aciertos de caché y resultados vacíos). |
quotaAntes de correr un lote grande, llamá a GET /apidev/v1/geocoding/quota para ver tu saldo remaining. Si un lote se queda sin saldo a mitad de camino, resuelve tantos ítems como el saldo permita y devuelve un error por ítem para el resto — ver Forward Geocoding (lote).
Forward Geocoding
Convierte una dirección en una coordenada. Enviá una dirección estructurada (país / departamento / calle / número / esquina, o ruta + km) o texto libre. Devuelve candidatos rankeados con precisión, confianza y componentes de dirección normalizados.
/apidev/v1/geocoding/forwardCuerpo de la solicitud
Enviá un objeto de consulta único, o un lote con { "items": [ ... ] } (1–25 consultas). Ver Modo lote.
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
mode | string | No | auto (por defecto), street_number, intersection, route_km o freetext. auto infiere la estrategia según los campos que envíes. |
country | string | Condicional | País (ID o nombre). Acota la búsqueda. Requerido salvo en freetext y route_km. |
department | string | Condicional | Departamento / provincia (ID o nombre). Desambigua calles homónimas. Requerido salvo en freetext. |
city | string | No | Ciudad / localidad (ID o nombre). Sesga la búsqueda. |
street | string | Condicional | Nombre de calle. Acepta una línea completa como "Av. Brasil 2950 esq. Ellauri"; el motor la separa. |
number | string | No | Número de puerta. Activa el modo street_number. |
corner | string | No | Esquina. Activa el modo intersection. |
route | string | Condicional | Número de ruta. Combinado con km, activa el modo route_km. |
km | number | Condicional | Kilómetro / mojón. |
q | string | Condicional | Texto libre. Activa el modo freetext. |
providers | string[] | No | Subconjunto de internal, nominatim, google, mapbox, bing. Se intersecta con los proveedores habilitados para tu compañía; se conserva el orden. |
limit | number | No | Máximo de candidatos a devolver. Mín 1, Máx 25, por defecto 10. |
lang | string | No | Idioma de los resultados (es, en, …). |
bias_bbox | number[4] | No | Caja [minLng, minLat, maxLng, maxLat] para sesgar la búsqueda hacia un área. |
Campos de la respuesta
data.results es un arreglo de candidatos. Cada candidato tiene:
| Campo | Tipo | Descripción |
|---|---|---|
lat | string | Latitud (decimal como string). |
lng | string | Longitud (decimal como string). |
precision | string | Calidad del resultado: explicit, rooftop, range, intersection, route-km, street, approx o centroid. |
confidence | number | null | Confianza del proveedor, de 0 a 1. |
source | string | Proveedor que lo resolvió: internal, nominatim, google, mapbox o bing. |
address | object | Componentes normalizados: street, number, corner, neighborhood, city, department, country, postcode. |
formatted | string | Dirección legible en una sola línea. |
bbox | number[4] | Caja contenedora [minLng, minLat, maxLng, maxLat]. Presente cuando está disponible. |
normalized | object | Presente solo cuando se corrigió la calle: { street, resolved_from } (la calle usada vs. el texto que enviaste). |
El bloque meta incluye providers_tried (string[]), resolved_by (string | null), cached (boolean) y el bloque quota descrito en Cómo funciona el cobro.
Ejemplo de código
- cURL
- JavaScript
- Python
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"
}'
const response = await fetch(
`https://${TENANT}/apidev/v1/geocoding/forward`,
{
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"X-API-Key": API_KEY,
"tenant": TENANT,
"Content-Type": "application/json",
},
body: JSON.stringify({
country: "Uruguay",
department: "Montevideo",
street: "Av. Brasil",
number: "2950",
}),
}
);
const { data, meta } = await response.json();
const best = data.results[0];
console.log(`${best.lat}, ${best.lng} — ${meta.quota.remaining} credits left`);
response = requests.post(
f"https://{TENANT}/apidev/v1/geocoding/forward",
headers={**headers, "Content-Type": "application/json"},
json={
"country": "Uruguay",
"department": "Montevideo",
"street": "Av. Brasil",
"number": "2950",
},
)
result = response.json()
best = result["data"]["results"][0]
print(f"{best['lat']}, {best['lng']} — charged {result['meta']['quota']['charged']}")
Respuesta de ejemplo
{
"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
}
}
}
Una búsqueda válida que no encuentra nada devuelve 200 con "results": [] y "charged": 0. Solo se cobra cuando una búsqueda realmente resuelve una ubicación.
Modo lote
Enviá hasta 25 consultas a la vez con { "items": [ ... ] }. La forma de la respuesta cambia: data.items es un arreglo de { index, success, results?, error? }, y meta.quota.charged reporta el total de créditos consumidos en el lote.
Si el saldo se agota a mitad de camino, los ítems resueltos hasta ese punto se cobran y se devuelven; cada ítem restante vuelve con success: false y un error GEOCODING_QUOTA_EXCEEDED. La solicitud igual devuelve 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
Convierte una coordenada en una dirección. Mismo cobro que forward — 1 crédito por resultado, gratis en acierto de caché o resultado vacío.
/apidev/v1/geocoding/reverseCuerpo de la solicitud
Enviá un objeto de consulta único, o un lote con { "items": [ ... ] } (1–25 consultas, con las mismas reglas de cobro parcial que forward).
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
lat | string | Sí | Latitud (decimal como string). |
lng | string | Sí | Longitud (decimal como string). |
lang | string | No | Idioma de los resultados (es, en, …). |
zoom | number | No | Nivel de detalle. Mín 3, Máx 18. |
Campos de la respuesta
La misma forma de candidato que Forward Geocoding. meta.providers_tried es ["nominatim"], y meta.quota lleva el saldo después del cobro.
Ejemplo de código
- cURL
- JavaScript
- Python
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"
}'
const response = await fetch(
`https://${TENANT}/apidev/v1/geocoding/reverse`,
{
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ lat: "-34.90568300", lng: "-56.18820200" }),
}
);
const { data, meta } = await response.json();
console.log(`${data.results[0]?.formatted} — charged ${meta.quota.charged}`);
response = requests.post(
f"https://{TENANT}/apidev/v1/geocoding/reverse",
headers={**headers, "Content-Type": "application/json"},
json={"lat": "-34.90568300", "lng": "-56.18820200"},
)
result = response.json()
print(result["data"]["results"][0]["formatted"])
Respuesta de ejemplo
{
"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
}
}
}
Geocoding interno (nuestros mapas)
Resuelve una dirección solo contra la base de calles propia de LogicSat — sin Nominatim, sin proveedores externos. Vos elegís un mode estructurado explícito, así no hay ambigüedad: cada modo mapea directo a una búsqueda contra nuestros mapas.
Usalo cuando ya tenés la dirección separada en campos y querés la coincidencia más precisa en los países cubiertos, o cuando querés evitar específicamente los proveedores externos.
mode | Resuelve | Campos requeridos | Cobertura |
|---|---|---|---|
street_corner | Calle y su esquina (intersección) | country, street, corner | Uruguay, Argentina, Ecuador, Chile, Paraguay, Colombia |
street_number | Calle y número de puerta | country, street, number | Uruguay y Argentina (los países con rangos de numeración) |
route | Ruta y mojón / kilómetro | route, km | Uruguay |
/apidev/v1/geocoding/internalEl geocoding interno se cobra igual que forward y reverse: 1 crédito por resultado que resuelve, gratis en aciertos de caché y resultados vacíos. Solo autocomplete y quota son siempre gratis.
Cuerpo de la solicitud
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
mode | string | Sí | street_corner, street_number o route. Selecciona la búsqueda estructurada. |
country | string | Condicional | País (ID o nombre). Requerido para street_corner y street_number. Ignorado en route (solo Uruguay). |
department | string | No | Departamento / provincia (ID o nombre). Desambigua calles homónimas. |
street | string | Condicional | Nombre de calle. Requerido para street_corner y street_number. |
corner | string | Condicional | Esquina. Requerido para street_corner. |
number | string | Condicional | Número de puerta. Requerido para street_number. |
route | string | Condicional | Número de ruta. Requerido para route. |
km | string | Condicional | Kilómetro / mojón. Requerido para route. |
Campos de la respuesta
La misma forma de candidato que Forward Geocoding. Cada resultado tiene source: "internal" y meta.providers_tried es ["internal"]. precision es intersection para street_corner, range para street_number (el motor devuelve el centroide del tramo del rango, no la puerta interpolada) y route-km para route. meta.quota lleva el saldo después del cobro.
Ejemplo de código
- cURL
- JavaScript
- Python
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"
}'
const response = await fetch(
`https://${TENANT}/apidev/v1/geocoding/internal`,
{
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({
mode: "street_number",
country: "Uruguay",
department: "Montevideo",
street: "Av. Brasil",
number: "2950",
}),
}
);
const { data, meta } = await response.json();
const best = data.results[0];
console.log(`${best.lat}, ${best.lng} — ${meta.quota.remaining} credits left`);
response = requests.post(
f"https://{TENANT}/apidev/v1/geocoding/internal",
headers={**headers, "Content-Type": "application/json"},
json={
"mode": "route",
"route": "1",
"km": "110",
},
)
result = response.json()
best = result["data"]["results"][0]
print(f"{best['lat']}, {best['lng']} — charged {result['meta']['quota']['charged']}")
Respuesta de ejemplo
{
"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
Sugerencias de typeahead para un nombre de calle. Gratis — no devuelve coordenadas y nunca consume un crédito.
/apidev/v1/geocoding/autocompleteCuerpo de la solicitud
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
country | string | Sí | País (ID o nombre). |
department | string | No | Departamento / provincia (ID o nombre). Acota las sugerencias. |
street | string | Sí | Fragmento de calle a buscar. Mínimo 2 caracteres. |
limit | number | No | Máximo de sugerencias. Mín 1, Máx 20, por defecto 10. |
Campos de la respuesta
data.suggestions es un arreglo de { street, neighborhood?, city?, department? }. meta.cached indica si el resultado vino de la caché corta.
Ejemplo de código
- cURL
- JavaScript
- Python
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"
}'
const response = await fetch(
`https://${TENANT}/apidev/v1/geocoding/autocomplete`,
{
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ country: "Uruguay", department: "Montevideo", street: "bras" }),
}
);
const { data } = await response.json();
data.suggestions.forEach((s) => console.log(s.street));
response = requests.post(
f"https://{TENANT}/apidev/v1/geocoding/autocomplete",
headers={**headers, "Content-Type": "application/json"},
json={"country": "Uruguay", "department": "Montevideo", "street": "bras"},
)
for s in response.json()["data"]["suggestions"]:
print(s["street"])
Respuesta de ejemplo
{
"success": true,
"data": {
"suggestions": [
{
"street": "Avenida Brasil",
"neighborhood": "Pocitos",
"department": "Montevideo"
},
{
"street": "Brasilia",
"neighborhood": "Carrasco",
"department": "Montevideo"
}
]
},
"meta": {
"cached": false
}
}
Quota
Consultá el saldo de geocoding de tu compañía — contratado, usado, restante y el período activo. Gratis, y nunca toca tu cupo.
/apidev/v1/geocoding/quotaCampos de la respuesta
| Campo | Tipo | Descripción |
|---|---|---|
contracted | number | Créditos totales del período activo. |
used | number | Créditos consumidos en el período activo. |
remaining | number | Créditos restantes. |
period_start | string | null | Inicio de la ventana de facturación activa. |
period_end | string | null | Fin de la ventana de facturación activa. |
active | boolean | true cuando hay una ventana contratada vigente. false (con remaining: 0) cuando el geocoding no está contratado o la ventana venció. |
Ejemplo de código
- cURL
- JavaScript
- Python
curl -s "https://$TENANT/apidev/v1/geocoding/quota" \
-H "Authorization: Bearer $TOKEN" \
-H "X-API-Key: $APIKEY" \
-H "tenant: $TENANT"
const response = await fetch(
`https://${TENANT}/apidev/v1/geocoding/quota`,
{ headers }
);
const { data } = await response.json();
console.log(`${data.remaining} of ${data.contracted} credits left`);
response = requests.get(
f"https://{TENANT}/apidev/v1/geocoding/quota",
headers=headers,
)
data = response.json()["data"]
print(f"{data['remaining']} of {data['contracted']} credits left")
Respuesta de ejemplo
{
"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": {}
}
Las fechas del período se devuelven sin zona horaria (p. ej., "2026-01-01T00:00:00"). El valor representa la zona horaria configurada de la compañía. No agregues Z ni apliques conversión a UTC; mostralo tal cual.
Errores
Todos los endpoints de esta página pueden devolver estos errores. Consultá Manejo de errores para la referencia completa.
| Código | HTTP | Aplica a | Descripción |
|---|---|---|---|
VALIDATION_ERROR | 400 | Todos | Campos del cuerpo inválidos (campo desconocido, tipo incorrecto, campo requerido ausente, lote de más de 25 ítems). Detalle en details[]. |
UNAUTHORIZED | 401 | Todos | tenant / Authorization / X-API-Key ausente, inválido o expirado. |
FORBIDDEN | 403 | Todos | La clave de API no tiene el permiso APICLI_GEOCODE. |
GEOCODING_NOT_CONTRACTED | 402 | Forward, Reverse, Interno | El geocoding no está contratado para tu compañía, o la ventana de facturación venció. Contratá el servicio de geocodificación con LogicSat o renová la ventana. |
GEOCODING_QUOTA_EXCEEDED | 402 | Forward, Reverse, Interno | Se agotó el saldo de geocodificación del período. Esperá la renovación o ampliá tu cupo contratado con LogicSat. |
RATE_LIMITED | 429 | Todos | Se superaron 30 solicitudes/min. Es un límite por tiempo, distinto del cupo por uso. |
INTERNAL_ERROR | 500 | Todos | Error inesperado del servidor. Reintentá; si persiste, contactá a soporte. |
Los dos códigos 402 llevan el saldo actual directamente en error.details para que tu integración sepa exactamente por qué se rechazó la llamada. El cupo se contrata con LogicSat — recargalo o renová la ventana para seguir usando 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
}
}
}
Relacionado
- Autenticación
- Límites de solicitudes — Límite por tiempo (distinto del cupo por uso)
- Manejo de errores — Referencia completa de errores