Skip to main content

Error Codes

Every error response from HatiData includes a structured error code, a human-readable message, and a stable machine-readable identifier. This page documents all error codes, their meaning, and what an agent should do when it encounters each one.

Machine-Readable Errors

All error responses follow a consistent JSON format across the SQL proxy, MCP server, and control plane API:

{
"error": {
"code": "HATI_ERR_401_EXPIRED_KEY",
"status": 401,
"message": "API key has expired. Generate a new key in the dashboard.",
"request_id": "req_8f3a2b1c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"timestamp": "2025-01-15T10:30:00.123Z",
"details": {}
}
}
FieldTypeDescription
codestringStable error identifier (e.g., HATI_ERR_401_EXPIRED_KEY). Safe to match on programmatically.
statusintegerHTTP status code. Mirrors the response status.
messagestringHuman-readable explanation. May change between releases -- do not match on this.
request_idstringUnique request ID for support troubleshooting. Always include this when filing issues.
timestampstringISO 8601 timestamp of the error.
detailsobjectOptional structured metadata. Contents vary by error type.

For SQL proxy connections (Postgres wire protocol), errors are returned as standard Postgres ErrorResponse messages with SQLSTATE codes. The HATI_ERR_* code is included in the DETAIL field.


Error Code Reference

400 -- Bad Request

Client sent a malformed or unsupported request.

CodeMessageDescriptionAgent Action
HATI_ERR_400_BAD_SQLSQL parse errorThe SQL statement has a syntax error or uses unsupported syntax. The details.position field indicates where parsing failed.Fix the SQL syntax. Do not retry the same query. Check SQL Compatibility for supported syntax.
HATI_ERR_400_INVALID_IDENTIFIERInvalid identifierA table or column name contains characters that are not allowed, or exceeds the 128-character limit.Correct the identifier. Use only alphanumeric characters, underscores, and dots for schema-qualified names.
HATI_ERR_400_INVALID_PARAMSInvalid request parametersA required parameter is missing or has an invalid value. The details.field indicates which parameter.Check the details object and correct the parameter value.
HATI_ERR_400_QUERY_TOO_LARGEQuery exceeds size limitThe SQL statement exceeds the maximum allowed size (1 MB).Break the query into smaller statements. For bulk inserts, use COPY or file imports instead.
HATI_ERR_400_UNSUPPORTED_TYPEUnsupported data typeA column definition uses a type that HatiData cannot transpile.Use a supported type. See SQL Functions for the type mapping reference.

401 -- Unauthorized

Authentication failed or credentials are missing.

CodeMessageDescriptionAgent Action
HATI_ERR_401_MISSING_AUTHNo authentication providedThe request does not include an API key, JWT, or other credential.Include an ApiKey header (MCP), Authorization: Bearer <jwt> header (control plane), or password (SQL proxy).
HATI_ERR_401_INVALID_KEYAPI key is not validThe provided API key does not match any active key. It may have been deleted or was never valid.Verify the API key. Generate a new key from the dashboard if the original was revoked. Do not retry with the same key.
HATI_ERR_401_EXPIRED_KEYAPI key has expiredThe API key passed its expiration date.Generate a new API key in the dashboard. Do not retry with the expired key.
HATI_ERR_401_EXPIRED_TOKENJWT has expiredThe JWT's exp claim is in the past.Request a new JWT from the control plane token endpoint. The SDK handles this automatically if you use client.connect().
HATI_ERR_401_INVALID_TOKENJWT signature verification failedThe JWT signature does not match, or the token is malformed.Re-authenticate. If using a custom JWT flow, verify you are signing with the correct key.

403 -- Forbidden

Authentication succeeded, but the identity does not have permission for the requested action.

CodeMessageDescriptionAgent Action
HATI_ERR_403_SCOPE_VIOLATIONOperation not allowed by key scopeThe API key's scope does not permit this operation (e.g., a read-only key attempted a write).Use an API key with the required scope, or request elevated permissions from your org admin.
HATI_ERR_403_CROSS_TENANTCross-tenant access deniedThe request attempted to access data belonging to a different organization.Verify the org_id in your credentials matches the target resource. This is a hard boundary -- cross-tenant access is never allowed. Escalate to your admin if you believe the org mapping is wrong.
HATI_ERR_403_ENV_MISMATCHEnvironment ownership mismatchThe API key or JWT is bound to a different environment (e.g., a staging key used against production).Use a key or token issued for the correct environment.
HATI_ERR_403_TABLE_DENIEDTable access denied by policyRow-level or table-level access policy prevents this identity from accessing the specified table.Check your assigned roles and ABAC attributes. Request access from your org admin if needed.
HATI_ERR_403_COLUMN_MASKEDColumn access restrictedOne or more columns in the query are masked or hidden for this identity. The query will still execute, but masked columns return NULL or redacted values.This is informational. The query succeeded with masked data. If you need unmasked access, request elevated permissions.

404 -- Not Found

The requested resource does not exist.

CodeMessageDescriptionAgent Action
HATI_ERR_404_ENV_NOT_FOUNDEnvironment not foundThe specified environment ID does not exist or has been deleted.Verify the environment ID. List available environments via GET /v1/environments.
HATI_ERR_404_TABLE_NOT_FOUNDTable not foundThe referenced table does not exist in the current schema. The details.schema and details.table fields identify the lookup.Check the table name and schema. Use SHOW TABLES or information_schema.tables to list available tables.
HATI_ERR_404_AGENT_NOT_FOUNDAgent identity not foundThe agent ID referenced in the request does not exist in the agent registry.Register the agent first via the MCP register_agent tool or the control plane API.
HATI_ERR_404_TRIGGER_NOT_FOUNDTrigger not foundThe specified trigger ID does not exist.List available triggers with the MCP list_triggers tool and verify the ID.
HATI_ERR_404_BRANCH_NOT_FOUNDBranch not foundThe specified branch ID does not exist or has been discarded.List active branches with the MCP branch_list tool. Discarded branches cannot be recovered.

429 -- Rate Limited

The request was rejected because a rate or quota limit has been exceeded.

CodeMessageDescriptionAgent Action
HATI_ERR_429_RATE_LIMITRate limit exceededToo many requests in the current window. The Retry-After header indicates how many seconds to wait.Wait for the duration specified in the Retry-After header, then retry. Implement exponential backoff for sustained bursts.
HATI_ERR_429_QUOTA_EXCEEDEDMonthly quota exceededThe organization has consumed its monthly query quota. Queries are blocked until the next billing cycle or until the quota is increased.Do not retry -- the quota resets at the start of the next billing period. Upgrade the plan or contact support to increase the quota.
HATI_ERR_429_CONCURRENT_LIMITToo many concurrent queriesThe maximum number of concurrent queries (semaphore) is reached.Retry after a short delay (1-2 seconds). Reduce query parallelism if this occurs frequently. Check HATIDATA_MAX_CONCURRENT_QUERIES configuration.

500 -- Internal Server Error

An unexpected error occurred on the server.

CodeMessageDescriptionAgent Action
HATI_ERR_500_INTERNALInternal server errorAn unclassified server-side failure. The request_id is critical for debugging.Retry once after a brief delay. If the error persists, report it with the request_id to support.
HATI_ERR_500_DUCKDB_CRASHQuery engine failureThe DuckDB execution engine encountered an unrecoverable error on this query. Other queries are not affected.Do not retry the same query immediately -- it may trigger the same crash. Simplify the query or break it into smaller parts. Report the request_id to support.
HATI_ERR_500_TRANSPILE_FAILSQL transpilation failedThe SQL was parsed successfully but could not be transpiled from Snowflake dialect to DuckDB.Check SQL Compatibility for known limitations. If the syntax should be supported, report it as a bug with the original SQL.

502 -- Bad Gateway

The proxy could not reach an upstream service.

CodeMessageDescriptionAgent Action
HATI_ERR_502_PROXY_UNREACHABLEUpstream proxy unreachableThe control plane or client could not connect to the data plane proxy.Verify network connectivity and that the proxy is running. For VPC deployments, check security group and PrivateLink configuration. Retry after 5-10 seconds.
HATI_ERR_502_CONTROL_PLANE_DOWNControl plane unavailableThe data plane proxy could not reach the control plane for policy refresh or authentication. Cached policies continue to be enforced.This is usually transient. Retry after 10-30 seconds. If persistent, check the control plane service health at GET /health.

503 -- Service Unavailable

The service is temporarily unable to handle requests.

CodeMessageDescriptionAgent Action
HATI_ERR_503_CIRCUIT_OPENCircuit breaker openRepeated failures have triggered the circuit breaker. The service is temporarily rejecting requests to allow recovery.Wait for the circuit breaker cooldown period (default: 30 seconds), then retry. The Retry-After header indicates when the circuit will close.
HATI_ERR_503_SHUTTING_DOWNService shutting downThe proxy is in the process of graceful shutdown. In-flight queries will complete, but new queries are rejected.Reconnect to a different instance or wait for the service to restart. In cloud deployments, the load balancer will route to a healthy instance automatically.
HATI_ERR_503_MAINTENANCEScheduled maintenanceThe environment is undergoing planned maintenance.Wait for maintenance to complete. Check the HatiData status page for estimated completion time.

Error Handling in the Python SDK

The Python SDK raises typed exceptions that map to the error codes above:

from hatidata import Client, HatiDataError, AuthenticationError, RateLimitError

client = Client(
host="your-env.hatidata.com",
port=5439,
api_key="hd_live_...",
)

try:
result = client.query("SELECT * FROM analytics.events LIMIT 10")
for row in result:
print(row)

except AuthenticationError as e:
# HATI_ERR_401_* errors
# Do not retry -- fix the credentials
print(f"Auth failed: {e.code} - {e.message}")
print(f"Action: Re-generate your API key in the dashboard")

except RateLimitError as e:
# HATI_ERR_429_* errors
# Respect Retry-After header
import time
print(f"Rate limited: retry after {e.retry_after}s")
time.sleep(e.retry_after)
result = client.query("SELECT * FROM analytics.events LIMIT 10")

except HatiDataError as e:
# All other HATI_ERR_* errors
print(f"Error {e.code}: {e.message}")
print(f"Request ID: {e.request_id}")
if e.status == 500:
print("Server error -- report this request_id to support")

Retry Decision Table

Use this table to determine whether an error is safe to retry:

StatusRetry?Strategy
400NoFix the request. The same input will always fail.
401NoRe-authenticate, then retry with new credentials.
403NoRequest elevated permissions. Do not retry with the same identity.
404NoVerify the resource exists before retrying.
429YesWait for Retry-After seconds, then retry. Use exponential backoff.
500OnceRetry once after 1-2 seconds. If it fails again, escalate.
502YesRetry with exponential backoff (2s, 4s, 8s). Max 3 attempts.
503YesWait for Retry-After or 30 seconds, then retry.

Stay in the loop

Product updates, engineering deep-dives, and agent-native insights. No spam.