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": {}
}
}
| Field | Type | Description |
|---|---|---|
code | string | Stable error identifier (e.g., HATI_ERR_401_EXPIRED_KEY). Safe to match on programmatically. |
status | integer | HTTP status code. Mirrors the response status. |
message | string | Human-readable explanation. May change between releases -- do not match on this. |
request_id | string | Unique request ID for support troubleshooting. Always include this when filing issues. |
timestamp | string | ISO 8601 timestamp of the error. |
details | object | Optional 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.
| Code | Message | Description | Agent Action |
|---|---|---|---|
HATI_ERR_400_BAD_SQL | SQL parse error | The 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_IDENTIFIER | Invalid identifier | A 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_PARAMS | Invalid request parameters | A 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_LARGE | Query exceeds size limit | The 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_TYPE | Unsupported data type | A 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.
| Code | Message | Description | Agent Action |
|---|---|---|---|
HATI_ERR_401_MISSING_AUTH | No authentication provided | The 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_KEY | API key is not valid | The 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_KEY | API key has expired | The 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_TOKEN | JWT has expired | The 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_TOKEN | JWT signature verification failed | The 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.
| Code | Message | Description | Agent Action |
|---|---|---|---|
HATI_ERR_403_SCOPE_VIOLATION | Operation not allowed by key scope | The 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_TENANT | Cross-tenant access denied | The 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_MISMATCH | Environment ownership mismatch | The 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_DENIED | Table access denied by policy | Row-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_MASKED | Column access restricted | One 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.
| Code | Message | Description | Agent Action |
|---|---|---|---|
HATI_ERR_404_ENV_NOT_FOUND | Environment not found | The 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_FOUND | Table not found | The 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_FOUND | Agent identity not found | The 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_FOUND | Trigger not found | The specified trigger ID does not exist. | List available triggers with the MCP list_triggers tool and verify the ID. |
HATI_ERR_404_BRANCH_NOT_FOUND | Branch not found | The 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.
| Code | Message | Description | Agent Action |
|---|---|---|---|
HATI_ERR_429_RATE_LIMIT | Rate limit exceeded | Too 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_EXCEEDED | Monthly quota exceeded | The 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_LIMIT | Too many concurrent queries | The 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.
| Code | Message | Description | Agent Action |
|---|---|---|---|
HATI_ERR_500_INTERNAL | Internal server error | An 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_CRASH | Query engine failure | The 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_FAIL | SQL transpilation failed | The 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.
| Code | Message | Description | Agent Action |
|---|---|---|---|
HATI_ERR_502_PROXY_UNREACHABLE | Upstream proxy unreachable | The 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_DOWN | Control plane unavailable | The 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.
| Code | Message | Description | Agent Action |
|---|---|---|---|
HATI_ERR_503_CIRCUIT_OPEN | Circuit breaker open | Repeated 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_DOWN | Service shutting down | The 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_MAINTENANCE | Scheduled maintenance | The 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:
| Status | Retry? | Strategy |
|---|---|---|
| 400 | No | Fix the request. The same input will always fail. |
| 401 | No | Re-authenticate, then retry with new credentials. |
| 403 | No | Request elevated permissions. Do not retry with the same identity. |
| 404 | No | Verify the resource exists before retrying. |
| 429 | Yes | Wait for Retry-After seconds, then retry. Use exponential backoff. |
| 500 | Once | Retry once after 1-2 seconds. If it fails again, escalate. |
| 502 | Yes | Retry with exponential backoff (2s, 4s, 8s). Max 3 attempts. |
| 503 | Yes | Wait for Retry-After or 30 seconds, then retry. |