For developers
Connect your fleet to your system.
A REST API and webhooks to get positions, trips and alerts into your rental, logistics or accounting system.
Version 1 is stable: we only add fields and endpoints. Breaking changes go to /api/v2. Alerts, geofences, drivers, commands and rentals endpoints are coming next.
The basics
A simple, stable and documented API.
-
REST and JSON
Paths under /api/v1, ISO-8601 UTC timestamps, speed in km/h, distances in metres and km.
-
Scoped tokens
Bearer tokens created in the panel, each with only the abilities you pick, an optional IP allowlist and expiry.
-
Webhooks
Events are pushed to your HTTPS endpoint, signed with HMAC-SHA256, retried for up to 24 hours.
-
Clear errors
Every error is application/problem+json (RFC 9457) with a status and a readable detail.
-
Cursor pagination
Lists return next_cursor; pass it back as ?cursor with ?limit up to 100.
-
Clear limits
Per-organisation rate limit (60 per minute by default, more on higher plans) with X-RateLimit-* headers.
Authentication
Bearer tokens with abilities
Owners and admins create tokens in Panel > Integrations. The token is shown once. Every request sends it as Authorization: Bearer. A token stops working when it is revoked, expires, or its owner loses the matching permission.
vehicles:readList and read vehiclesvehicles:writeEdit name, plate, group and speed limitpositions:readLive state of one or all vehicleshistory:readPosition history and GPX routesreports:readTrips, stops and summarieswebhooks:manageCreate, list and delete webhooksalerts:readAlerts (endpoints coming soon)geofences:writeGeofences (endpoints coming soon)commands:writeDevice commands (endpoints coming soon)
curl "https://gps.appalbania.com/api/v1/vehicles?limit=2" \
-H "Authorization: Bearer $GPS_TOKEN" \
-H "Accept: application/json"
{
"data": [
{
"id": 42,
"name": "Iveco Daily",
"plate": "AA 123 BB",
"type": "van",
"speed_limit_kmh": 90,
"group": { "id": 3, "name": "Tirana" },
"device": { "id": 17, "unique_id": "356307042441013", "model": "Teltonika FMB920", "status": "active" },
"archived": false,
"updated_at": "2026-10-06T09:41:12Z"
}
],
"next_cursor": "eyJ2ZWhpY2xlcy5pZCI6NDIsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0",
"prev_cursor": null,
"has_more": true,
"limit": 2
}
Endpoints
Base URL https://gps.appalbania.com/api/v1. Times in ISO-8601 UTC. History ranges: at most 7 days for positions, 31 days for reports.
| Method | Path | Ability | Description |
|---|---|---|---|
GET |
/me |
any token | The token, its owner and organisation |
GET |
/usage |
any token | Rate limit, requests, vehicles and webhooks counters |
GET |
/vehicles |
vehicles:read
|
List vehicles (?q, ?group_id, ?archived, ?cursor, ?limit) |
GET |
/vehicles/{id} |
vehicles:read
|
One vehicle |
PATCH |
/vehicles/{id} |
vehicles:write
|
Change name, plate, group_id, speed_limit_kmh |
GET |
/states |
positions:read
|
Live state of all vehicles |
GET |
/vehicles/{id}/state |
positions:read
|
Live state: lat, lng, speed_kmh, course, ignition, status |
GET |
/vehicles/{id}/positions |
history:read
|
Positions between ?from and ?to |
GET |
/vehicles/{id}/route.gpx |
history:read
|
The same track as a GPX file |
GET |
/vehicles/{id}/trips |
reports:read
|
Trips between ?from and ?to |
GET |
/vehicles/{id}/stops |
reports:read
|
Stops between ?from and ?to |
GET |
/vehicles/{id}/summary |
reports:read
|
Distance, speeds, engine hours for a period |
GET |
/webhooks |
webhooks:manage
|
List webhooks |
POST |
/webhooks |
webhooks:manage
|
Create a webhook (returns the secret once) |
DELETE |
/webhooks/{id} |
webhooks:manage
|
Delete a webhook |
GET |
/webhooks/{id}/deliveries |
webhooks:manage
|
Delivery log, newest first |
GET |
/devices/catalog |
none | Supported device models and capabilities (no token) |
Live state
One status per vehicle
status is moving above 5 km/h, parked with ignition off, idle otherwise, and offline when the last fix is older than 10 minutes. You only ever see data from the moment a device was assigned to your organisation.
{
"data": {
"vehicle_id": 42,
"status": "moving",
"lat": 41.3275,
"lng": 19.8187,
"speed_kmh": 54.2,
"course": 128,
"altitude": 112.0,
"ignition": true,
"fix_time": "2026-10-06T09:41:12Z",
"device_time": "2026-10-06T09:41:12Z"
}
}
Errors, pages and limits
Predictable answers
Errors are application/problem+json. Another organisation’s ids answer 404, never 403.
401Missing, wrong, expired or revoked token403The token lacks the ability (see "ability"), or the IP is not allowed404Not found, or not yours422Invalid fields (see "errors"), or a range that is too long423Organisation is read-only (suspended): only GET works429Rate limit hit: wait Retry-After seconds503Tracking data temporarily unavailable: retry shortly
Pagination, limits, retries
- Lists answer {"data": [...], "next_cursor": "…", "has_more": true}. Pass next_cursor as ?cursor until it is null.
- X-RateLimit-Limit and X-RateLimit-Remaining on every answer; the limit is per organisation, shared by all its tokens.
- Send an Idempotency-Key header on POST: the same key and body within 24 hours returns the first answer (Idempotent-Replayed: true).
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
{
"type": "https://gps.appalbania.com/en/developers#error-forbidden",
"title": "Forbidden",
"status": 403,
"detail": "This token does not have the history:read ability.",
"instance": "/api/v1/vehicles/42/positions",
"ability": "history:read"
}
curl -X POST "https://gps.appalbania.com/api/v1/webhooks" \
-H "Authorization: Bearer $GPS_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2a9e-erp-setup" \
-d '{"url":"https://erp.example.com/gps","events":["alert.created","trip.ended"]}'
Webhooks
Events come to you
Instead of polling, register an HTTPS URL. We POST {id, type, created_at, tenant_id, data} and expect a 2xx answer within 10 seconds.
- Header X-GPS-Signature: t=<unix>,v1=<hex HMAC-SHA256 of t + "." + raw body> with your secret
- Reject timestamps older than 5 minutes; use the event id to ignore duplicates
- Failed deliveries are retried with exponential backoff for up to 24 hours
- After 20 failures in a row the webhook is disabled and the owner is emailed
- Private, local and link-local addresses are refused
Events
alert.createdNew alertignition.onIgnition onignition.offIgnition offtrip.startedTrip startedtrip.endedTrip endedgeofence.enterEntered a geofencegeofence.exitLeft a geofencevehicle.status_changedStatus changeddevice.onlineDevice onlinedevice.offlineDevice offlinecommand.resultCommand resultposition.updatedNew position (opt-in, at most every 30 s per vehicle)
// Header: X-GPS-Signature: t=1791279672,v1=5f2b…
[$t, $v1] = sscanf($_SERVER['HTTP_X_GPS_SIGNATURE'] ?? '', 't=%d,v1=%s');
$body = file_get_contents('php://input');
$expected = hash_hmac('sha256', $t.'.'.$body, getenv('GPS_WEBHOOK_SECRET'));
if (! $v1 || ! hash_equals($expected, $v1) || abs(time() - $t) > 300) {
http_response_code(401);
exit;
}
$event = json_decode($body, true); // $event['type'], $event['data']
http_response_code(204);
import crypto from 'node:crypto';
import express from 'express';
const app = express();
app.post('/gps/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const parts = Object.fromEntries((req.get('X-GPS-Signature') || '')
.split(',').map((pair) => pair.split('=')));
const t = Number(parts.t);
const expected = crypto.createHmac('sha256', process.env.GPS_WEBHOOK_SECRET)
.update(`${t}.${req.body}`).digest('hex');
const valid = typeof parts.v1 === 'string' && parts.v1.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
&& Math.abs(Date.now() / 1000 - t) <= 300;
if (!valid) return res.sendStatus(401);
const event = JSON.parse(req.body); // event.type, event.data
res.sendStatus(204);
});
# body.json = the raw request body, t and v1 from X-GPS-Signature
t=1791279672
expected=$(printf '%s.%s' "$t" "$(cat body.json)" \
| openssl dgst -sha256 -hmac "$GPS_WEBHOOK_SECRET" | sed 's/^.* //')
[ "$expected" = "$v1" ] && echo valid || echo invalid
# Replay a captured delivery against your endpoint
curl -X POST https://erp.example.com/gps \
-H "Content-Type: application/json" \
-H "X-GPS-Signature: t=$t,v1=$expected" \
--data-binary @body.json
Recipe for rental systems
How to connect the platform to your rent-a-car software today.
- 1
Map the cars
GET /v1/vehicles?q=<plate or VIN> once and store the vehicle id in your system.
- 2
Follow the rental
Subscribe to ignition.on, trip.ended and alert.created, or poll GET /v1/states for the live position.
- 3
Close with the numbers
At return, GET /v1/vehicles/{id}/summary?from=<pickup>&to=<return> gives the distance and odometer.
Need another endpoint?
Tell us which system you use and what data you need.