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 onmessageor on the HTTP status alone. - Treat
401and403as permanent. Retrying will not help and just burns your quota. - Treat
429as temporary and honourRetry-After. - Treat
5xxas temporary and retry with exponential backoff. - Log the whole
errorobject.typeplusmessageis usually enough for us to tell you what went wrong without needing your key.
Last updated 26 August 2026