Skip to content
GIMLabsDocs

Conventions

Response shape, paging, incremental sync and rate limits.

Response shape

Every successful response is wrapped:

{ "data": ..., "meta": { "nextCursor": null } }

data is an object for a single resource and an array for a list. meta only appears where there is something to say.

Every failure looks like this, with a stable machine-readable type:

{
  "error": {
    "type": "insufficient_scope",
    "message": "This API key lacks the 'tables:read' scope.",
    "docs": "https://docs.gimlabs.io/api/errors#insufficient_scope"
  }
}

Branch on type, never on message. Messages get reworded; types do not.

Paging

List endpoints are cursor-paged.

ParameterDefaultMax
limit100200
cursor--

Read meta.nextCursor and pass it back as ?cursor=. When it comes back null, you have everything.

curl -H "Authorization: Bearer $KEY" \
  "https://api.gimlabs.io/v1/projects?limit=100&cursor=6512a4b8c9d0e1f234567801"

Cursors are stable while data is being added underneath you, which offset paging is not. Do not build a cursor yourself or store one long-term; treat it as opaque.

Incremental sync

Every list endpoint accepts ?updatedSince=<ISO 8601> and returns only rows changed at or after that moment.

curl -H "Authorization: Bearer $KEY" \
  "https://api.gimlabs.io/v1/projects/{id}/tables/GEOL?updatedSince=2026-08-01T00:00:00Z"

This is how you keep a warehouse or a dashboard fresh without re-pulling everything. Store the timestamp of your last successful run and pass it next time.

Note

Deleted rows are simply absent; there is no tombstone. If you need to detect deletions, do a full pull periodically alongside your incremental ones.

Rate limits

Limits apply on two axes at once. Per key stops one runaway script. Per company is the real ceiling, since you can mint as many keys as you like.

Endpoints are grouped by how much work they cost us:

ClassEndpointsPer keyPer company
Cheap/me, /projects, /projects/{id}120/min600/min
Readlocations, tables60/min300/min

Plus a company-wide ceiling of 100,000 requests per month across all keys.

Every response carries your current state:

RateLimit-Limit: 60
RateLimit-Remaining: 58
RateLimit-Reset: 1787781060

RateLimit-Reset is a Unix timestamp in seconds.

When you hit a limit

You get 429 with a Retry-After header in seconds. Wait that long, then retry. Retry-After accounts for which window you actually blew, so a monthly breach tells you the truth rather than suggesting you try again in 40 seconds.

Back off rather than retrying in a tight loop. A sensible client watches RateLimit-Remaining and slows down before it gets refused at all.

Dates

All timestamps are UTC, ISO 8601, e.g. 2026-08-26T09:12:44.000Z. Rate-limit windows are UTC too, so a monthly allowance resets at midnight UTC on the 1st, not in your local timezone.

Values are strings

Inside a data object, values come back exactly as stored, as strings. We do not coerce them, because guessing types is how precision gets quietly lost in ground investigation data. Convert at your end.

The lifted location fields (easting, northing, latitude, longitude, groundLevel, finalDepth) are the deliberate exception: numbers, or null where the location has no such value.

Last updated 26 August 2026