Skip to content
GIMLabsDocs

Errors

Every error type the API returns, why it happens and how to fix it.

Errors always come back in the same envelope, with a stable type you can branch on. The docs field links straight to the relevant heading on this page.

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

unauthorized

401. No key, an unrecognised key, a revoked key, or an expired one. All four return the same response on purpose, so nobody can use the error to work out whether a key was ever real.

Check you are sending Authorization: Bearer <key> and that the key has not been revoked in Product OS. A key that "used to work" has almost always been revoked or has expired.

forbidden

403. The key is valid but the company behind it is not in a state that can be served - most often the company record has been removed. If a key stops working with this and nothing changed at your end, talk to us.

feature_not_available

403. Your company is on the Go plan, which does not include the API. Pro and legacy licences have it. Talk to us about upgrading.

subscription_inactive

403. The key and the plan are both fine, but the company's subscription is not currently active. Sort the billing out and it starts working again with no change to the key.

insufficient_scope

403. The key is valid but was not granted this capability. The message names the scope it wanted.

Scopes are fixed when the key is created and cannot be edited afterwards. Mint a new key with the right ones ticked, then revoke the old one.

not_found

404. The thing does not exist, has been deleted, belongs to another company, or is outside this key's project allow-list.

Note

Those last two are intentionally indistinguishable from a genuine 404. A restricted key must not be able to use error codes to discover what else you hold. If you are sure the id is right, check the key's project list in Product OS or via /me.

table_not_found

404. The table in the URL is not configured for that project. Codes are matched case-insensitively, so GEOL and geol are the same table.

Note the difference between this and an empty result: table_not_found means the project has no such table at all, whereas a 200 with "data": [] means the table exists but holds no rows.

invalid_id

400. The id in the URL is not a 24-character hexadecimal id. Usually this means a project reference like GL-2201 has been passed where the API wanted the id field from a list response.

invalid_cursor

400. The ?cursor= value is not a well-formed cursor. Pass back exactly what meta.nextCursor gave you, unchanged.

We refuse rather than ignore it on purpose: silently restarting at page 1 would let a client that corrupts its cursor page forever without ever noticing.

rate_limit_exceeded

429. You have gone past a limit. Retry-After tells you how many seconds to wait, and it reflects whichever window you actually blew, so a monthly breach will not tell you to retry in 40 seconds.

See rate limits for the numbers. If you are hitting them regularly, updatedSince usually means you are pulling far less data than you think you need to.

Handling errors well

  • Branch on type, never on message or on the HTTP status alone.
  • Treat 401 and 403 as permanent. Retrying will not help and just burns your quota.
  • Treat 429 as temporary and honour Retry-After.
  • Treat 5xx as temporary and retry with exponential backoff.
  • Log the whole error object. type plus message is usually enough for us to tell you what went wrong without needing your key.

Last updated 26 August 2026