Mensajería y Cercanos
Enviá un mensaje de texto libre a la app del móvil que corre en un vehículo, y encontrá los móviles más cercanos a un punto dado — útil para asignar la unidad más próxima a un trabajo.
Todos los endpoints requieren un token JWT válido, una clave de API y el encabezado tenant. Consultá Autenticación.
Enviar mensaje
Encola un mensaje de texto libre a la app del móvil de un único vehículo. Es sin garantía de entrega (fire-and-forget): una respuesta exitosa significa que el mensaje se encoló, no que se entregó ni que se leyó. La app del móvil lo recoge la próxima vez que consulta sus mensajes.
/apidev/v1/devices/{vehicle_id}/messagesParámetros de ruta
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
vehicle_id | string | Sí | Identificador único del vehículo (móvil). Debe pertenecer a tu tenant. |
Cuerpo de la solicitud
| Campo | Tipo | Requerido | Longitud máxima | Descripción |
|---|---|---|---|---|
text | string | Sí | 500 | Mensaje a enviar al móvil. No puede estar vacío. |
push | boolean | No | — | Disparar una notificación push (por defecto true). |
Hoy cada mensaje encolado también dispara una notificación push, por lo que push: false se acepta pero todavía no se respeta. Enviá true (u omitilo) para no depender de ese cambio futuro.
Campos de la respuesta
| Campo | Tipo | Descripción |
|---|---|---|
message_id | string | Identificador del mensaje encolado (BigInt como string). Puede omitirse si la cola no devolvió uno. |
vehicle_id | string | Eco del identificador del móvil destino. |
delivery | string | Siempre "queued" — el mensaje fue aceptado en la cola de entrega. Encolado no es lo mismo que entregado ni leído. |
Ejemplo de código
- cURL
- JavaScript
- Python
curl -s -X POST "https://$TENANT/apidev/v1/devices/1013/messages" \
-H "Authorization: Bearer $TOKEN" \
-H "X-API-Key: $APIKEY" \
-H "tenant: $TENANT" \
-H "Content-Type: application/json" \
-d '{
"text": "Head back to base, end of shift."
}'
const response = await fetch(
`https://${TENANT}/apidev/v1/devices/1013/messages`,
{
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"X-API-Key": API_KEY,
"tenant": TENANT,
"Content-Type": "application/json",
},
body: JSON.stringify({ text: "Head back to base, end of shift." }),
}
);
const { data } = await response.json();
console.log(`Message ${data.message_id} → ${data.delivery}`);
response = requests.post(
f"https://{TENANT}/apidev/v1/devices/1013/messages",
headers={**headers, "Content-Type": "application/json"},
json={"text": "Head back to base, end of shift."},
)
data = response.json()["data"]
print(f"Message {data['message_id']} → {data['delivery']}")
Respuesta de ejemplo
{
"success": true,
"data": {
"message_id": "8472910",
"vehicle_id": "1013",
"delivery": "queued"
},
"meta": {}
}
Si el vehículo no tiene un dispositivo activo que pueda recibir mensajes (sin IMEI activo), la solicitud falla con 409 CONFLICT. Esto es distinto de 404, que significa que el vehículo no existe en tu tenant. Asociá un dispositivo activo al móvil antes de enviarle mensajes.
Móviles cercanos
Encontrá los móviles más cercanos a un punto, ordenados por distancia. La búsqueda está acotada por un radio (hasta 50 km) y se puede afinar por la antigüedad del GPS, la validez del GPS y el tipo de vehículo.
/apidev/v1/devices/nearbyConceptualmente es una lectura, pero lleva un cuerpo estructurado (un punto más filtros) que no entra prolijo en parámetros de consulta, y realiza un cálculo de distancia. Sigue la misma convención que los endpoints de reportes, que también aceptan un cuerpo POST.
Cuerpo de la solicitud
| Campo | Tipo | Requerido | Por defecto | Descripción |
|---|---|---|---|---|
point | object | Sí | — | Centro de la búsqueda: { "lat": number, "lng": number }. lat en [-90, 90], lng en [-180, 180]. |
radius_m | number | Sí | — | Radio de búsqueda en metros. Mín 1, máx 50000 (50 km). |
limit | number | No | 20 | Cantidad máxima de resultados. Mín 1, máx 100. |
only_valid_gps | boolean | No | true | Solo móviles cuyo último GPS sea válido (coordenadas presentes, posición válida). |
max_age_min | number | No | — | Descarta móviles cuyo último GPS sea más viejo que N minutos. Mín 1, máx 1440. Si se omite, no se aplica límite de antigüedad. |
vehicle_types | string[] | No | — | Filtra por identificadores de tipo de vehículo (BigInt como strings). Hasta 100 valores. |
Campos de la respuesta
El campo data es un arreglo de móviles cercanos, ordenados por distancia ascendente.
| Campo | Tipo | Descripción |
|---|---|---|
vehicle_id | string | Identificador del vehículo (móvil) (BigInt como string). |
name | string | Nombre para mostrar del móvil. |
plate | string | Patente. |
vehicle_type_id | string | null | Identificador del tipo de vehículo (BigInt como string). |
point | object | Último punto GPS: { "lat": number, "lng": number }. |
distance_m | number | Distancia al point solicitado, en metros (redondeada). |
heading | number | null | Rumbo en grados (0–359), o null cuando no está disponible. |
speed | number | Velocidad en km/h del último punto GPS. |
last_signal | string | null | Marca de tiempo del último GPS válido (sin zona horaria). |
valid_gps | boolean | Si el último GPS es válido. |
heading es null cuando no hay rumbo disponible — nunca 0, que significaría "norte exacto". last_signal se devuelve sin zona horaria (p. ej., "2026-06-25T14:58:00") y representa la zona horaria configurada de la compañía; mostralo tal cual, sin agregar Z ni convertir a UTC.
Ejemplo de código
- cURL
- JavaScript
- Python
curl -s -X POST "https://$TENANT/apidev/v1/devices/nearby" \
-H "Authorization: Bearer $TOKEN" \
-H "X-API-Key: $APIKEY" \
-H "tenant: $TENANT" \
-H "Content-Type: application/json" \
-d '{
"point": { "lat": -34.9050, "lng": -56.1910 },
"radius_m": 5000,
"limit": 10,
"only_valid_gps": true,
"max_age_min": 30,
"vehicle_types": ["5"]
}'
const response = await fetch(
`https://${TENANT}/apidev/v1/devices/nearby`,
{
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"X-API-Key": API_KEY,
"tenant": TENANT,
"Content-Type": "application/json",
},
body: JSON.stringify({
point: { lat: -34.9050, lng: -56.1910 },
radius_m: 5000,
limit: 10,
only_valid_gps: true,
max_age_min: 30,
vehicle_types: ["5"],
}),
}
);
const { data, meta } = await response.json();
console.log(`${meta.total} mobiles within range`);
for (const m of data) {
console.log(`${m.name} — ${m.distance_m} m`);
}
response = requests.post(
f"https://{TENANT}/apidev/v1/devices/nearby",
headers={**headers, "Content-Type": "application/json"},
json={
"point": {"lat": -34.9050, "lng": -56.1910},
"radius_m": 5000,
"limit": 10,
"only_valid_gps": True,
"max_age_min": 30,
"vehicle_types": ["5"],
},
)
result = response.json()
for m in result["data"]:
print(f"{m['name']} — {m['distance_m']} m")
Respuesta de ejemplo
{
"success": true,
"data": [
{
"vehicle_id": "1013",
"name": "Grua 07",
"plate": "ABC 1234",
"vehicle_type_id": "5",
"point": { "lat": -34.9012, "lng": -56.1888 },
"distance_m": 342,
"heading": 270,
"speed": 0,
"last_signal": "2026-06-25T14:58:00",
"valid_gps": true
},
{
"vehicle_id": "1021",
"name": "Grua 12",
"plate": "DEF 5678",
"vehicle_type_id": "5",
"point": { "lat": -34.9100, "lng": -56.1950 },
"distance_m": 820,
"heading": 135,
"speed": 34,
"last_signal": "2026-06-25T14:59:30",
"valid_gps": true
}
],
"meta": {
"total": 2,
"limit": 10
}
}
Si ningún móvil cae dentro del radio — o todos quedan filtrados por only_valid_gps, max_age_min o las reglas de visibilidad — la respuesta es 200 con un arreglo data vacío. Cada móvil también está sujeto a las reglas de visibilidad de tu usuario técnico, por lo que solo ves los móviles que ese usuario tiene permitido ver.
El radio máximo es 50 km. Un radius_m mayor se rechaza con 400 en lugar de acotarse en silencio, para evitar un barrido accidental de toda la flota.
Errores
Consultá Manejo de errores para la referencia completa.
| Código | HTTP | Aplica a | Descripción |
|---|---|---|---|
VALIDATION_ERROR | 400 | Enviar mensaje, Cercanos | text vacío; o point fuera de rango, radius_m ausente o mayor a 50000, limit mayor a 100. |
UNAUTHORIZED | 401 | Todos | tenant / Authorization / X-API-Key ausente, inválido o expirado. |
FORBIDDEN | 403 | Todos | El usuario no tiene APICLI_DEVICE_MESSAGE (Enviar mensaje) o APICLI_FLEET_DEVICES_READ (Cercanos). |
NOT_FOUND | 404 | Enviar mensaje | El vehicle_id no existe o no pertenece a tu tenant. |
CONFLICT | 409 | Enviar mensaje | El móvil no tiene un dispositivo activo que pueda recibir mensajes (sin IMEI activo). |
RATE_LIMITED | 429 | Todos | Se superaron 20 solicitudes/min. |
INTERNAL_ERROR | 500 | Todos | Error inesperado del servidor. |
Relacionado
- API de Dispositivos — Lista, inspecciona, localiza y actualiza los móviles de tu flota
- API de Conductores — Administra los conductores asignados a los dispositivos
- Manejo de errores — Envelope y códigos de error estándar
- Límites de solicitudes — Cómo se aplican los límites de solicitudes