Error reference

Standard error response format, common status codes, and troubleshooting tips.

Error response format

Use Loam's JSON error responses to identify application failures and choose the right fix:

json
{
  "error": {
    "message": "A human-readable description of the error",
    "code": "ERROR_CODE"
  }
}
FieldTypeDescription
error.messagestringA human-readable explanation of what went wrong.
error.codestringA machine-readable error code you can match on programmatically.

HTTP status codes

StatusMeaningWhen it happens
400Bad RequestMalformed request body, invalid SQL syntax, or missing required fields.
401UnauthorizedMissing Authorization header, invalid JWT, or expired JWT.
403ForbiddenYour JWT is valid but you do not have permission for the requested operation (insufficient grants or role).
404Not FoundThe requested resource (Namespace, Table, View, grant, etc.) does not exist.
422Unprocessable EntityThe request is well-formed but contains invalid values (e.g., unknown role or invalid node ID).
500Internal Server ErrorAn unexpected server error. Retry idempotent requests with bounded exponential backoff. For writes, first verify whether the operation completed.

Common error codes

CodeHTTP StatusDescription
PARSE_ERROR400SQL syntax error. Check your query for typos.
PLAN_ERROR400Query planning failed. Usually means an unknown column name or type mismatch.
QUERY_RESOURCE_LIMIT_EXCEEDED400A relational statement exceeded a source-pinning limit before object reads. The fixed message is query resource limit exceeded. Reduce the number of referenced tables or views, columns, active parts, or catalog metadata; no partial query rows are returned. See the Query API source-pinning limits.
INVALID_CONFIG400Invalid request configuration (e.g., unsupported format value).
INVALID_TOKEN401The access JWT is missing, malformed, or expired. Get a fresh JWT and retry the request.
UNAUTHORIZED401Control-plane authentication failed, or API key exchange rejected an invalid, expired, or revoked key secret.
ACL_DENIED403Insufficient permissions. A node in the resolved Namespace ancestry denies access or lacks the required role.
NOT_FOUND404Resource does not exist. Check the absolute Namespace path, stable node ID, or grant ID.
MISSING_WORKSPACE400JWT does not contain a workspace ID. Re-exchange your API key secret.
INTERNAL_ERROR500Unexpected server error. Retry idempotent requests with bounded exponential backoff. For writes, first verify whether the operation completed.

Common errors and fixes

"Invalid or expired API key" (401)

The API key secret is incorrect, or the key is revoked or past its optional expiration date. In the Loam web app, go to Settings > API keys and check whether the key is still active. If it is revoked or expired, or you no longer have the stored secret, create a new key and exchange its secret. Revocation deactivates the Engine principal, so JWTs issued for that key fail on their next authorization check even if their signed expiry has not yet passed.

"table not found" (404)

The table does not exist or an absolute path component is wrong. Paths begin at the workspace root and may contain any supported Namespace depth, such as commerce.sales.production.charges. Use the catalog endpoints to recursively list visible nodes and their current names.

"insufficient permissions to manage grants" (403)

Grant management requires an effective human Admin grant on the target node or an authorized Namespace ancestor. Editors and machine principals cannot delegate access. A workspace owner receives an explicit root Admin grant; the owner role itself is not a data-plane bypass. Check the caller principal's current grants and the node's resolved ancestry.

JWT expired (401 after working previously)

JWTs expire after 15 minutes. Exchange your API key secret again to get a fresh JWT. See the Authentication guide for auto-refresh patterns.

"missing Authorization: Bearer <token>" (401)

The grants endpoint requires authentication. Send a current workspace-bound JWT in the Authorization header. The authenticated principal must also have the required admin grant.

Retry strategy

For idempotent requests that return 500, retry up to three times with exponential backoff:

typescript
async function retryIdempotent<T>(
  fn: () => Promise<T>,
  maxRetries = 3,
): Promise<T> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (err: any) {
      if (attempt === maxRetries || err.status !== 500) {
        throw err;
      }
      await new Promise((r) => setTimeout(r, 1000 * 2 ** attempt));
    }
  }
  throw new Error("unreachable");
}

Do not automatically retry writes unless the endpoint documents idempotency. For 400, 401, 403, or 404 errors, fix the request or credentials before trying again.

Last updated 2026-08-09