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 Fleet plan. A key on an account without it answers 403.

Calling it

curl -H "Authorization: Bearer sgps_..." \
     https://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:

  1. Put https://simple-gps.com/api/v1/devices in the address field, replacing the example that is there.
  2. Open the Authorization tab, set the type to Bearer, and paste your key into Token.
  3. Press Send. You should get the list of your vehicles back.
  4. Take any id from 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 (ImportPostman), 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.

One thing worth checking in the response Add 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

EndpointLimit
/devices120 requests per minute
Everything else30 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-LimitHow many requests this endpoint allows per minute. It differs by endpoint, so read it rather than assuming.
X-RateLimit-RemainingHow many you have left in the current minute.
Retry-AfterSeconds to wait. Sent only on a 429.
X-RateLimit-ResetSeconds 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.

Times and durations. Every timestamp is ISO 8601. Every duration is in seconds, never hours: rounding to hours is a decision that belongs to whoever is adding them up, not to us. And engine_seconds is the sum of the other two, not a third measurement - do not add all three together.
Which day is a day. Windows and daily buckets are resolved in the vehicle's timezone, which you get back in every response. A shift ending at 23:30 lands on the day it was worked rather than being split by a UTC midnight.

Endpoints

GET /api/v1/devices

Every vehicle on the account. This is your mapping table: our id never changes, so map it once against your own asset id.

{
  "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.

ParameterMeaning
fromDate (YYYY-MM-DD). Whole days in the vehicle's zone. Optional.
toSame. 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 - the figure that lines up against the hour meter on the machine's own dash.

When something is wrong

StatusWhat it means
401Missing, unknown, revoked or expired key. The message says which.
403The account is not on a plan that includes the API.
404No such vehicle on this account, or no such endpoint.
429Over 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.

Questions, or an endpoint you need that is not here: [email protected].