Overview
The GEO TELEMATIC Public API gives you programmatic, read-only access to the vehicles registered on your GEO TELEMATIC account: their live state, and their history (mileage, driving time, tracks, stops, alarms). It is designed for integrations such as dispatch systems, ERP/logistics software, dashboards, and third-party fleet platforms.
https://api.geotelematic.ge/v1Format — all requests and responses are JSON over HTTPS. All timestamps are Unix epoch seconds (UTC). Coordinates are WGS84.
Each API key is bound to a specific scope: by default it returns only the vehicles of your own account. A device outside your scope returns 404, as if it did not exist. Partner-level keys with access to a wider set of vehicles are issued case by case.
Getting an API key
API keys are issued manually by the GEO TELEMATIC team. To get one, contact us on WhatsApp and tell us:
- your company / account name on GEO TELEMATIC,
- what you are integrating (briefly),
- the expected polling frequency.
WhatsApp: +995 568 87 78 99
Authentication
Every request must carry your API key in the Authorization header using the Bearer scheme:
Authorization: Bearer gtk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Keys start with the prefix gtk_. A missing, malformed, or revoked key returns 401 Unauthorized. There is no token exchange or request signing — the header is all you need.
Conventions
| Topic | Rule |
|---|---|
| Time parameters | startTime / endTime are Unix timestamps in seconds (milliseconds are also accepted). endTime is optional and defaults to now. |
| Maximum period | The span between startTime and endTime cannot exceed 31 days. |
| Units | Distance in kilometres, speed in km/h, durations in seconds. |
| Days | Daily mileage is aggregated by local calendar day (Asia/Tbilisi, UTC+4). |
| Success | History endpoints return "code": 0 on success. Errors use HTTP status codes (see Errors). |
GET/public/positions
Returns the latest known position and status of every vehicle your key has access to. Only vehicles that have reported at least one position are included, so lat/lng are never null.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number, starting at 1. See Pagination. |
per_page | integer | 500 | Vehicles per page, 1–1000. |
imei | string | — | Only these devices, comma-separated: imei=861449050856266,865190074229124. |
fields | string | all | Return only these fields, comma-separated: fields=imei,lat,lng,speed. Smaller responses for frequent polling. |
active_minutes | integer | — | Only vehicles that reported in the last N minutes. |
moving_minutes | integer | per key | Same filter with minute precision: only vehicles that moved (speed > 3 km/h) or had the ignition on in the last N minutes (1–720), e.g. moving_minutes=5. 0 returns all vehicles. Takes priority over moving_hours; the value applied is echoed in the response. |
moving_hours | integer | per key | Only vehicles that moved (speed > 3 km/h) or had the ignition on in the last N hours (1–12). 0 returns all vehicles. Some keys have a default set on our side; the value applied is echoed in the response as moving_hours. |
Request
GET https://api.geotelematic.ge/v1/public/positions?page=1
Authorization: Bearer gtk_xxxxxxxx…
Response 200 OK
{
"pagination": {
"total": 21,
"per_page": 500,
"current_page": 1,
"total_pages": 1,
"has_next": false,
"has_prev": false
},
"generated_at": "2026-09-29T13:40:46.048Z",
"moving_hours": null,
"devices": [
{
"imei": "000009171863346",
"deviceName": "UV-032-VV",
"licenseNumber": "UV-032-VV",
"accountId": "3ee3871f-5faa-47d3-a14c-2160065f66d9",
"parentAccount": null,
"lat": 42.267771,
"lng": 42.706086,
"speed": 0,
"heading": 215,
"accStatus": 1,
"status": "online",
"locationType": "GPS",
"gpsTime": 1789911611,
"statusTime": 1789911611,
"offlineDuration": 35,
"address": null
}
]
}
Field descriptions: see Position fields.
GET/public/devices
The registry of devices your key can see — use it to detect devices that were added to or removed from your fleet. Same scope as /public/positions.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number. |
per_page | integer | 1000 | Devices per page, 1–1000. |
Response 200 OK
{
"pagination": { "total": 58, "per_page": 1000, "current_page": 1, "total_pages": 1 },
"generated_at": "2026-09-29T13:40:46.048Z",
"devices": [
{ "imei": "865190074229124", "deviceName": "AA-123-BB", "licenseNumber": "AA-123-BB",
"accountId": "…", "accountName": "…", "parentAccount": null,
"addedAt": "2026-05-02T08:11:00Z", "updatedAt": "…", "lastSeenAt": "…" }
]
}
GET/public/device/info NEW
Full details and current state of a single device, including its total mileage.
Query parameters
| Parameter | Required | Description |
|---|---|---|
imei | yes | Device IMEI. |
Response 200 OK
{ "code": 0, "data": {
"imei": "865190074229124", "deviceName": "AA-123-BB", "licenseNumber": "AA-123-BB",
"vin": null, "model": "H19P", "vehicleType": "car", "simNumber": "5XXXXXXXX",
"accountId": "…", "accountName": "…", "installedAt": "2026-05-02", "activatedAt": 1777700000,
"status": "online", "accStatus": 1, "speed": 42, "lat": 41.7151, "lng": 44.8271,
"gpsTime": 1790000000, "totalMiles": 1999.0, "overspeedLimit": 120
} }
totalMiles — total distance in km recorded by GEO TELEMATIC for this device.
GET/public/device/status/statistic NEW
How many of your vehicles are moving, standing, offline, or have never reported. No parameters.
{ "code": 0, "moveNum": 12, "staticNum": 40, "offlineNum": 5, "unusedNum": 1, "total": 58 }
Moving = online and speed above 3 km/h; standing = online and not moving; offline = not connected now; unused = never reported.
GET/public/device/miles NEW
Distance driven and driving time of a vehicle over any period — a day, a week, or any custom range up to 31 days.
Query parameters
| Parameter | Required | Description |
|---|---|---|
imei | yes | Device IMEI. |
startTime | yes | Period start, Unix seconds. |
endTime | no | Period end, Unix seconds. Defaults to now. |
Request
GET https://api.geotelematic.ge/v1/public/device/miles?imei=865190074229124&startTime=1759089600&endTime=1759176000
Response 200 OK
{ "code": 0, "imei": "865190074229124", "startTime": 1759089600, "endTime": 1759176000,
"miles": 83.68, "runTime": 17762 }
| Field | Description |
|---|---|
miles | Distance driven in the period, kilometres. GPS jumps and long signal gaps are filtered out. This is the same figure the GEO TELEMATIC app and reports show. |
runTime | Time the vehicle was moving with the ignition on, seconds. |
GET/public/device/track/history NEW
All recorded points of a vehicle for the period, oldest first.
Query parameters
| Parameter | Required | Description |
|---|---|---|
imei | yes | Device IMEI. |
startTime | yes | Period start, Unix seconds. |
endTime | no | Period end. Defaults to now. |
onlyGps | no | 1 = return only satellite (GPS) fixes. |
Response 200 OK
{ "code": 0, "imei": "865190074229124", "count": 3751, "truncated": false,
"data": [
{ "gpsTime": 1759090000, "lat": 41.7151, "lng": 44.8271, "speed": 38, "course": 120,
"satellites": 11, "acc": 1, "positionType": "GPS" }
] }
At most 100,000 points per request. If truncated is true, split the period into shorter requests.
GET/public/vehicle/stay NEW
Stops (parking) of a vehicle during the period.
Query parameters
| Parameter | Required | Description |
|---|---|---|
imei | yes | Device IMEI. |
startTime | yes | Period start, Unix seconds. |
endTime | no | Period end. Defaults to now. |
minMinutes | no | Minimum stop length in minutes, default 2. |
Response 200 OK
{ "code": 0, "imei": "865190074229124", "count": 16,
"data": [ { "startTime": 1759090000, "endTime": 1759093600, "duration": 3600, "lat": 41.7151, "lng": 44.8271 } ] }
A stop is a period with speed at or below 2 km/h lasting at least minMinutes — the same rule as the reports on the platform.
GET/public/device/alarm NEW
Alarms and events of a vehicle during the period: ignition on/off, overspeed, power cut, low voltage, geofence entry/exit, harsh driving and more. Up to 5,000 per request.
Query parameters
| Parameter | Required | Description |
|---|---|---|
imei | yes | Device IMEI. |
startTime | yes | Period start, Unix seconds. |
endTime | no | Period end. Defaults to now. |
Response 200 OK
{ "code": 0, "imei": "865190074229124", "count": 26,
"details": [ { "alarmType": "overspeed", "severity": "warning", "alarmName": "…", "message": "…",
"lat": 41.7151, "lng": 44.8271, "alarmTime": 1759090000 } ] }
Common alarmType values: ignition_on, ignition_off, overspeed, power_cut, power_restored, low_voltage, offline, geofence_enter, geofence_exit, harsh_accel, harsh_brake, harsh_corner, sos.
GET/public/vehicle/acc NEW
How many times the ignition was switched on, per vehicle, during the period.
Query parameters
| Parameter | Required | Description |
|---|---|---|
startTime | yes | Period start, Unix seconds. |
endTime | no | Period end. Defaults to now. |
imei | no | Only these devices, comma-separated. Without it, the whole fleet is counted and the period is limited to 7 days. |
Response 200 OK
{ "code": 0, "startTime": 1759089600, "endTime": 1759176000,
"data": [ { "imei": "865190074229124", "accCount": 14 } ] }
Vehicles with no ignition-on in the period are not listed.
GET/public/fence/query NEW
Geofences of the account the vehicle belongs to (read only).
Query parameters
| Parameter | Required | Description |
|---|---|---|
imei | yes | Device IMEI. |
Response 200 OK
{ "code": 0, "imei": "865190074229124",
"fenceBeanList": [ { "fenceId": "…", "fenceName": "Warehouse", "radius": 300,
"alarmOnEnter": true, "alarmOnExit": true, "lat": 41.7151, "lng": 44.8271,
"geometry": { "type": "Polygon", "coordinates": [ … ] } } ] }
geometry is GeoJSON (for polygon fences); circular fences have lat/lng and radius in metres.
GET/public/account/tree NEW
Accounts available to your key. No parameters.
{ "code": 0, "data": [ { "accountId": "…", "userName": "My Company LLC", "parentAccountId": null } ] }
Position fields
Fields returned by /public/positions.
| Field | Type | Description |
|---|---|---|
imei | string | Device IMEI. Stable, unique identifier of the tracker — use this as your primary key. |
deviceName | string | Vehicle name as shown on the GEO TELEMATIC platform (usually the plate number). |
licenseNumber | string · null | License plate, if set separately from the name. |
accountId | string (UUID) | The account the vehicle belongs to. |
parentAccount | string · null | Parent account UUID for sub-accounts, otherwise null. |
lat / lng | number | Last known position, WGS84 decimal degrees. Never null. |
speed | integer | Speed at the last position, km/h. |
heading | integer · null | Direction of travel, degrees (0 = north, clockwise). |
accStatus | 0 · 1 | Ignition: 1 = on, 0 = off. |
status | "online" · "offline" | Whether the tracker is currently connected to the platform. |
locationType | string | Position source. Currently always "GPS". |
gpsTime | integer | Unix timestamp (seconds, UTC) of the last position. |
statusTime | integer | Unix timestamp (seconds, UTC) of the last contact. Currently equal to gpsTime. |
offlineDuration | integer | Seconds elapsed since the last contact. |
address | null | Reserved. Reverse-geocoded address is not included in this version; always null. |
Pagination
/public/positions and /public/devices are paginated. Results come in pages of 500 (positions) or 1000 (devices) by default; change it with per_page (up to 1000). The pagination object tells you how many pages exist. Most accounts fit on a single page.
# fetch every page
page = 1
while True:
r = get(f"/public/positions?page={page}")
process(r["devices"])
if not r["pagination"]["has_next"]: break
page += 1
Requesting a page beyond total_pages returns an empty devices array.
Rate limits
Each key is limited to 120 requests per minute by default. A full refresh of a typical fleet is one request, so polling once every 30–60 seconds is well within the limit. If your integration needs a higher limit, tell us when requesting the key.
Positions on the platform update as trackers report in (typically every few seconds while a vehicle is moving), so polling more often than every ~10–15 seconds brings no benefit. History endpoints (mileage, tracks, stops) do not need polling — call them when you need a report.
Errors
Errors are returned as JSON with an HTTP status code:
{ "statusCode": 400, "error": "Bad Request", "message": "the span cannot exceed 31 days" }
| Status | Meaning | What to do |
|---|---|---|
401 | Missing, malformed, revoked, or unknown API key. | Check the Authorization header. Contact us if the key was revoked. |
400 | Invalid parameter: missing imei / startTime, endTime before startTime, or a period longer than allowed. | Fix the request; the message says what is wrong. |
404 | The device does not exist or is not available to your key. | Check the IMEI. |
429 | Rate limit exceeded. | Slow down; wait the number of seconds in the Retry-After header. |
5xx | Temporary server-side problem. | Retry with exponential back-off (start at 5 s). |
Code examples
Fetch the whole fleet's live positions, and the mileage of one vehicle for yesterday.
# live positions
curl -s \
-H "Authorization: Bearer $GEO_API_KEY" \
"https://api.geotelematic.ge/v1/public/positions?page=1"
# mileage and driving time for a period
curl -s \
-H "Authorization: Bearer $GEO_API_KEY" \
"https://api.geotelematic.ge/v1/public/device/miles?imei=865190074229124&startTime=1759089600&endTime=1759176000"import os, time, requests
BASE = "https://api.geotelematic.ge/v1"
H = {"Authorization": "Bearer " + os.environ["GEO_API_KEY"]}
def all_positions():
page = 1
while True:
r = requests.get(f"{BASE}/public/positions", headers=H, params={"page": page}, timeout=15)
r.raise_for_status()
data = r.json()
yield from data["devices"]
if not data["pagination"]["has_next"]: break
page += 1
for v in all_positions():
print(v["deviceName"], v["lat"], v["lng"], v["speed"], "ON" if v["accStatus"] else "OFF")
# mileage for the last 24 hours
now = int(time.time())
m = requests.get(f"{BASE}/public/device/miles", headers=H,
params={"imei": "865190074229124", "startTime": now - 86400, "endTime": now}, timeout=30).json()
print(m["miles"], "km,", round(m["runTime"] / 3600, 1), "h driving")const BASE = "https://api.geotelematic.ge/v1";
const H = { Authorization: `Bearer ${process.env.GEO_API_KEY}` };
async function allPositions() {
const out = [];
for (let page = 1; ; page++) {
const r = await fetch(`${BASE}/public/positions?page=${page}`, { headers: H });
if (!r.ok) throw new Error(`HTTP ${r.status}`);
const data = await r.json();
out.push(...data.devices);
if (!data.pagination.has_next) break;
}
return out;
}
async function miles(imei, startTime, endTime) {
const q = new URLSearchParams({ imei, startTime, endTime });
const r = await fetch(`${BASE}/public/device/miles?${q}`, { headers: H });
return r.json(); // { miles, runTime }
}
allPositions().then(v => console.log(v.length, "vehicles"));<?php
$base = 'https://api.geotelematic.ge/v1';
$key = getenv('GEO_API_KEY');
function geo_get($url, $key) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $key"],
CURLOPT_TIMEOUT => 30,
]);
$data = json_decode(curl_exec($ch), true); curl_close($ch);
return $data;
}
$page = 1; $all = [];
do {
$data = geo_get("$base/public/positions?page=$page", $key);
$all = array_merge($all, $data['devices'] ?? []);
$page++;
} while (!empty($data['pagination']['has_next']));
echo count($all), " vehicles\n";
$now = time();
$m = geo_get("$base/public/device/miles?imei=865190074229124&startTime=" . ($now - 86400) . "&endTime=$now", $key);
echo $m['miles'], " km\n";Migrating from iopgps
If your integration used the iopgps API, the equivalent GEO TELEMATIC endpoints use the same field names wherever possible (miles, runTime, accStatus, gpsTime, fenceBeanList…). Authentication is simpler: no token exchange and no signature, just the Authorization: Bearer header.
| iopgps endpoint | GEO TELEMATIC endpoint |
|---|---|
/api/device/miles | /public/device/miles |
/api/device/track/history | /public/device/track/history |
/api/vehicle/stay/v2 | /public/vehicle/stay |
/api/device/alarm | /public/device/alarm |
/api/vehicle/acc | /public/vehicle/acc |
/api/device/status/statistic | /public/device/status/statistic |
/api/device/info, /api/device/detail | /public/device/info |
/api/fence/query | /public/fence/query |
/api/account/tree | /public/account/tree |
/api/device/location, /api/device/status, /api/vehicle/location/v2 | /public/positions (with ?imei=) |
/api/device, /api/device/detail/page | /public/devices |
/api/auth | Not needed — use the Authorization: Bearer header. |
Coordinates are always WGS84; the Chinese map systems (bd09ll, gcj02) are not used.
Best practices
- Key by IMEI. Names and plates can be edited on the platform; the IMEI is stable.
- Poll, don't hammer. Every 30–60 s is plenty for live positions. Use
fieldsandactive_minutesto keep frequent polls small, andgpsTimeto detect whether a vehicle has actually moved since your last poll. - Ask for history when you need it. Mileage, tracks and stops are reports — request them per day or per week instead of polling.
- Handle
offlinegracefully. An offline vehicle still returns its last known position — checkstatusandofflineDurationbefore treating the position as current. - Store the key in an environment variable or secret manager — never in source code.
- Retry on 5xx / network errors with exponential back-off; do not retry on 400, 401 or 404.
Changelog
| Date | Change |
|---|---|
| 2026-09-29 | New history and report endpoints: /device/miles (mileage and driving time for any period), /device/track/history, /vehicle/stay, /device/alarm, /vehicle/acc, /device/status/statistic, /device/info, /fence/query, /account/tree. New moving_hours filter on /positions. Migration guide from iopgps. |
| 2026-09-21 | /public/devices device registry. /positions: per_page, fields, active_minutes, imei parameters and the heading field. |
| 2026-09-20 | Public API v1 released: GET /public/positions with Bearer-key authentication, pagination and per-account scoping. |
GEO TELEMATIC