SimpleGPS API
Read the vehicles on your account and the hours they ran. Built for pulling numbers into your own system instead of typing them into a spreadsheet.
Getting a key
Issue one yourself in Settings → API keys. It is shown once, at creation - we store only a fingerprint, so if it is lost you issue another rather than asking us to read it back. Revoke a key from the same screen and it stops working immediately.
API access is part of the Business plan. A key on an account without it answers 403.
Calling it
curl -H "Authorization: Bearer sgps_..." \
https://www.simple-gps.com/api/v1/devices
Every response is JSON.
Trying it without writing any code
Two ways, both a couple of minutes. Have your key ready - issue it in Settings, API keys.
Hoppscotch, in the browser
Free, nothing to install and no account needed. Open hoppscotch.io and:
- Put
https://www.simple-gps.com/api/v1/devicesin the address field, replacing the example that is there. - Open the Authorization tab, set the type to Bearer, and paste your key into Token.
- Press Send. You should get the list of your vehicles back.
- Take any
idfrom that response and try the other three - the Authorization tab keeps your key, so you only change the address.
Postman, with the collection
If you would rather have all four requests ready: Download the Postman collection and import it. Paste your key into the api_key variable, then run Devices first - it saves the first vehicle id into a variable so the rest work without you copying anything.
Hoppscotch can import the same file (Import → Postman), but it does not carry the variables across, so you would set those by hand. For a first look the four clicks above are quicker.
moving_seconds and idle_seconds together. They should equal engine_seconds exactly - it is the sum of the two, not a third measurement. Adding all three counts every hour twice, and it is the mistake most likely to reach a report before anyone notices.
Rate limits
| Endpoint | Limit |
|---|---|
/devices | 120 requests per minute |
| Everything else | 30 requests per minute |
Counted per key, so your limit is yours alone. The second tier is lower because those calls read through position history for the window you ask for - listing vehicles is a single indexed lookup and costs almost nothing.
Over the limit answers 429 with a Retry-After header in seconds. Please obey it rather than retrying straight away. A fleet pulling hourly, or once a night, will never come near these numbers - if your integration needs more, tell us what it does and we will raise them.
Knowing where you are
You do not have to count. Every response carries two headers:
X-RateLimit-Limit | How many requests this endpoint allows per minute. It differs by endpoint, so read it rather than assuming. |
X-RateLimit-Remaining | How many you have left in the current minute. |
Retry-After | Seconds to wait. Sent only on a 429. |
X-RateLimit-Reset | Seconds until the counter goes back to full. Sent only on a 429. |
If you are polling on a schedule you will never see these move. They are there for the case where something loops by mistake - read X-RateLimit-Remaining and you will know before you are refused rather than after.
engine_seconds is the sum of the other two, not a third measurement - do not add all three together.
Endpoints
GET /api/v1/devices
Every vehicle on the account. This is your mapping table: our id belongs to the device record and does not change while that record exists - so map it once against your own asset id. One case does change it: a unit replaced under warranty is a new record with a new id. If what you are tracking is the physical box rather than the slot in the fleet, map on imei instead.
{
"data": [
{
"id": 41,
"name": "Forklift 3",
"imei": "860813070448084",
"registration": null,
"model": "Teltonika FTM305",
"status": "active",
"timezone": "America/Chicago",
"last_reported_at": "2026-08-26T14:03:11+00:00"
}
]
}
GET /api/v1/devices/{id}/hours
Totals for a window. Defaults to the last seven days.
| Parameter | Meaning |
|---|---|
from | Date (YYYY-MM-DD). Whole days in the vehicle's zone. Optional. |
to | Same. Optional; defaults to now. |
Windows reaching further back than 365 days are clamped rather than refused, and the window you actually received is echoed back - so a gap is never charted as zeroes.
{
"device_id": 41,
"from": "2026-08-01T05:00:00+00:00",
"to": "2026-08-08T04:59:59+00:00",
"timezone": "America/Chicago",
"engine_seconds": 141120,
"moving_seconds": 98400,
"idle_seconds": 42720
}
GET /api/v1/devices/{id}/hours/daily
The same three numbers per day. This is usually what replaces a spreadsheet.
{
"device_id": 41,
"timezone": "America/Chicago",
"data": [
{ "date": "2026-08-01", "engine_seconds": 21600, "moving_seconds": 15300, "idle_seconds": 6300 }
]
}
GET /api/v1/devices/{id}/meter
The running total since the tracker started reporting. It counts from the day the tracker was fitted, not over the machine's whole life, so compare it with what the machine's own hour meter has added since then.
When something is wrong
| Status | What it means |
|---|---|
401 | Missing, unknown, revoked or expired key. The message says which. |
403 | The account is not on a plan that includes the API. |
404 | No such vehicle on this account, or no such endpoint. |
429 | Over the rate limit. Wait and retry. |
A vehicle belonging to another account answers 404, not 403 - otherwise the difference between the two would tell you which of our ids exist.
Versioning
The version is in the path. Once a field means something to your nightly job, we will not change what it means - anything new goes somewhere new. If a version is ever retired we will write to the address on the account at least six months beforehand, and it keeps answering for that whole period. A deprecation nobody was told about is a breakage with a nicer name.
Questions, or an endpoint you need that is not here: [email protected].