Guards · Check-SQL
Check a SQL statement
Endpoint
https://claix.dev/check-sqlSend the statement an agent wants to run and Claix answers whether the active guards of that connection allow it. The check is deterministic: it reads the SQL, applies the published guards, and returns a verdict.
Always call https://claix.dev/check-sql.
Built for server-to-server integrations. The caller decides what to do with the verdict. Claix returns the decision and leaves execution to that system.
1. Authentication
Every request must include your API key. It is a server credential, separate from any session token.
Option A — dedicated header (recommended):
x-api-key: <YOUR_API_KEY>
Option B — standard Authorization header:
Authorization: Bearer <YOUR_API_KEY>
Either one is enough. If you send both, x-api-key wins.
Claix checks that the key exists, is active, and the account is not suspended. A failure returns 401.
2. Prerequisites
- An active database connection (PostgreSQL, MySQL, or MariaDB).
- At least one active guard on that connection if you want writes limited. With no active guards, the write is allowed.
- If the account has more than one active connection, the
connection_idof the one you want to check.
3. Request format
Method: POST · Content-Type: application/json
The body is a JSON object. Only sql is required.
| Field | Type | Required | Description |
|---|---|---|---|
| sql | string | Yes | A single statement. Maximum 200,000 characters. PostgreSQL placeholders are $1, $2…. MySQL and MariaDB use ?. |
| params | array | No | Placeholder values, in the same order. Up to 5,000 items. Accepts string, number, boolean, and null. |
| connection_id | uuid | Conditional | Connection to check. Omit it when the account has one active connection. With several, it is required. |
PostgreSQL example:
{
"sql": "INSERT INTO shop.orders (customer_id, total) VALUES ($1, $2)",
"params": ["c-1042", 6200],
"connection_id": "8f14e45f-ceea-4e6f-8b23-1e2d3c4b5a6f"
}On MySQL or MariaDB the same statement uses ?: VALUES (?, ?). The request body stays the same. The whole body can be up to 1 MB.
- Text comparisons distinguish case, accents, and trailing spaces.
- Qualify table names (
schema.tableon PostgreSQL,database.tableon MySQL). A name that exists in more than one schema is denied. - A column that a guard reads and the INSERT leaves out denies the statement.
4. How to build the call
- Have your API key and the statement the agent proposes.
- If the SQL uses placeholders, put the values in
paramsin the same order. - If there is more than one active connection, add its
connection_id. - POST JSON to
https://claix.dev/check-sqlwith the authentication header. - A 200 means the check finished. Read
allowed:trueallows it andfalsedenies it. The code and the reason are incodeandreason.
5. Call examples
See the panel on the right for cURL, JavaScript, Node.js, Python, PHP, and n8n examples.
6. Response format
200 OK · Content-Type: application/json
A 200 covers both an allow and a denial. The decision is in allowed. The sql field repeats the statement that was checked, as it was sent, in both cases. If Guard Traces has an alert endpoint, Claix POSTs this same JSON to the allowed URL or the denied URL.
Allowed:
{
"allowed": true,
"decision_id": "1a2b3c4d-5e6f-4789-a012-3456789abcde",
"code": "allowed",
"reason": "Ninguna regla prohíbe esta operación.",
"operation": "insert",
"table": "shop.orders",
"sql": "INSERT INTO shop.orders (customer_id, total) VALUES ($1, $2)",
"affected_rows": 1,
"violations": [],
"rules_checked": 1,
"latency_ms": 840
}Denied:
{
"allowed": false,
"decision_id": "9ff38974-1392-4c1a-83e5-3b2f09bb43dc",
"code": "rule_denied",
"reason": "No se pueden crear pedidos con un total superior a 5000.",
"operation": "insert",
"table": "shop.orders",
"sql": "INSERT INTO shop.orders (customer_id, total) VALUES ($1, $2)",
"affected_rows": 1,
"violations": [
{
"guard_id": "b7c1d2e3-4f56-4789-a012-3456789abcde",
"guard_name": "Límite de total en pedidos",
"rule_id": "e2a9c4b1-6d70-4e85-9f12-abcdef012345",
"rule_title": "Total máximo",
"code": "rule_denied",
"message": "No se pueden crear pedidos con un total superior a 5000."
}
],
"rules_checked": 1,
"latency_ms": 1104
}| Field | Description |
|---|---|
| allowed | true when the statement may proceed. false when it is denied. |
| decision_id | Identifier of this check. Use it to find the check in Guard Traces in the dashboard. |
| code | Short verdict code. See the table below. |
| reason | Human-readable explanation of the decision. |
| operation | insert, update, or delete when the statement writes. unknown for reads, session statements, and SQL that could not be classified. |
| table | Qualified table of the write, or null when the statement does not target a single table. |
| sql | The statement that was checked, as it was sent. Present for both allow and deny. |
| affected_rows | Rows the statement would affect. null when they cannot be counted. |
| violations | Rules that deny the statement. Empty when allowed is true. |
| rules_checked | Number of rules evaluated. |
| latency_ms | Milliseconds the check took. |
Each item in violations includes:
| Field | Description |
|---|---|
| guard_id | Guard identifier. null when the denial does not come from a specific guard. |
| guard_name | Guard name. |
| rule_id | Rule identifier. |
| rule_title | Rule title. |
| code | Same kind of code as the verdict. |
| message | Text that explains that violation. |
Verdict codes returned with HTTP 200:
| code | Meaning |
|---|---|
| allowed | The write satisfies the guards that cover it. |
| no_guards | The connection has no active guards, so the statement is allowed. |
| no_matching_guard | Active guards exist, and none cover this operation and this table. |
| read_only | It is a read. Guards limit writes. |
| session_statement | It is a session or transaction setting. |
| rule_denied | An active rule forbids the statement. |
| omitted_column | A rule reads a column the INSERT does not send. |
| rule_unknown | A value the rule needs is missing and the guard denies that case. |
| unsupported_statement | This SQL form cannot be certified and the connection has active guards. |
| parse_error | The SQL could not be read. |
| multiple_statements | The request contains more than one statement. |
| ambiguous_table | The table name exists in more than one schema. |
| unqualified_table | The table is not qualified and does not belong to the connection schema. |
| table_not_found | The table is not on the connection. |
| truncate_guarded | TRUNCATE on a table watched by a DELETE guard. |
| upsert_update | An INSERT that also updates, while an UPDATE guard watches the table. |
| replace_deletes | REPLACE, while a DELETE guard watches the table. |
| check_failed | The check could not finish. The statement is denied. |
7. Common errors
These HTTP statuses are call failures. A denial of the statement is still 200 with allowed: false.
| HTTP | error | When |
|---|---|---|
| 401 | missing_api_key | The API key is missing from x-api-key and from Authorization: Bearer. |
| 401 | invalid_api_key | The API key does not exist. |
| 401 | inactive_api_key | The API key is disabled. |
| 401 | expired_api_key | The API key has expired. |
| 401 | inactive_user | The API key account is not active. |
| 400 | invalid_json | The body is not a JSON object. |
| 400 | missing_sql | sql is missing or empty. |
| 400 | invalid_params | params is not an array. |
| 400 | invalid_connection_id | connection_id is not a UUID. |
| 400 | connection_required | There are several active connections and connection_id is missing. |
| 400 | unsupported_engine | The connection is not PostgreSQL, MySQL, or MariaDB. |
| 404 | connection_not_found | There is no active connection with that id. |
| 405 | method_not_allowed | Method other than POST. |
| 413 | body_too_large | The body is larger than 1 MB. |
| 413 | sql_too_large | sql is longer than 200,000 characters. |
| 413 | too_many_params | params has more than 5,000 items. |
| 500 | internal_error | Internal checker error. |
Error example:
{
"error": "missing_sql",
"message": "Falta \"sql\": la sentencia que el agente quiere ejecutar."
}The message field is returned in Spanish.