Guards

Check-SQL

Check a SQL statement against the active guards of a connected database. The call returns whether it is allowed.

Guards · Check-SQL

Check a SQL statement

Endpoint

POSThttps://claix.dev/check-sql

Send 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

  1. An active database connection (PostgreSQL, MySQL, or MariaDB).
  2. At least one active guard on that connection if you want writes limited. With no active guards, the write is allowed.
  3. If the account has more than one active connection, the connection_id of 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.

FieldTypeRequiredDescription
sqlstringYesA single statement. Maximum 200,000 characters. PostgreSQL placeholders are $1, $2…. MySQL and MariaDB use ?.
paramsarrayNoPlaceholder values, in the same order. Up to 5,000 items. Accepts string, number, boolean, and null.
connection_iduuidConditionalConnection 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.table on PostgreSQL, database.table on 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

  1. Have your API key and the statement the agent proposes.
  2. If the SQL uses placeholders, put the values in params in the same order.
  3. If there is more than one active connection, add its connection_id.
  4. POST JSON to https://claix.dev/check-sql with the authentication header.
  5. A 200 means the check finished. Read allowed: true allows it and false denies it. The code and the reason are in code and reason.

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
}
FieldDescription
allowedtrue when the statement may proceed. false when it is denied.
decision_idIdentifier of this check. Use it to find the check in Guard Traces in the dashboard.
codeShort verdict code. See the table below.
reasonHuman-readable explanation of the decision.
operationinsert, update, or delete when the statement writes. unknown for reads, session statements, and SQL that could not be classified.
tableQualified table of the write, or null when the statement does not target a single table.
sqlThe statement that was checked, as it was sent. Present for both allow and deny.
affected_rowsRows the statement would affect. null when they cannot be counted.
violationsRules that deny the statement. Empty when allowed is true.
rules_checkedNumber of rules evaluated.
latency_msMilliseconds the check took.

Each item in violations includes:

FieldDescription
guard_idGuard identifier. null when the denial does not come from a specific guard.
guard_nameGuard name.
rule_idRule identifier.
rule_titleRule title.
codeSame kind of code as the verdict.
messageText that explains that violation.

Verdict codes returned with HTTP 200:

codeMeaning
allowedThe write satisfies the guards that cover it.
no_guardsThe connection has no active guards, so the statement is allowed.
no_matching_guardActive guards exist, and none cover this operation and this table.
read_onlyIt is a read. Guards limit writes.
session_statementIt is a session or transaction setting.
rule_deniedAn active rule forbids the statement.
omitted_columnA rule reads a column the INSERT does not send.
rule_unknownA value the rule needs is missing and the guard denies that case.
unsupported_statementThis SQL form cannot be certified and the connection has active guards.
parse_errorThe SQL could not be read.
multiple_statementsThe request contains more than one statement.
ambiguous_tableThe table name exists in more than one schema.
unqualified_tableThe table is not qualified and does not belong to the connection schema.
table_not_foundThe table is not on the connection.
truncate_guardedTRUNCATE on a table watched by a DELETE guard.
upsert_updateAn INSERT that also updates, while an UPDATE guard watches the table.
replace_deletesREPLACE, while a DELETE guard watches the table.
check_failedThe 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.

HTTPerrorWhen
401missing_api_keyThe API key is missing from x-api-key and from Authorization: Bearer.
401invalid_api_keyThe API key does not exist.
401inactive_api_keyThe API key is disabled.
401expired_api_keyThe API key has expired.
401inactive_userThe API key account is not active.
400invalid_jsonThe body is not a JSON object.
400missing_sqlsql is missing or empty.
400invalid_paramsparams is not an array.
400invalid_connection_idconnection_id is not a UUID.
400connection_requiredThere are several active connections and connection_id is missing.
400unsupported_engineThe connection is not PostgreSQL, MySQL, or MariaDB.
404connection_not_foundThere is no active connection with that id.
405method_not_allowedMethod other than POST.
413body_too_largeThe body is larger than 1 MB.
413sql_too_largesql is longer than 200,000 characters.
413too_many_paramsparams has more than 5,000 items.
500internal_errorInternal checker error.

Error example:

{
  "error": "missing_sql",
  "message": "Falta \"sql\": la sentencia que el agente quiere ejecutar."
}

The message field is returned in Spanish.

Request examples

curl -X POST "https://claix.dev/check-sql" \
  -H "x-api-key: <TU_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{  "sql": "INSERT INTO shop.orders (customer_id, total) VALUES ($1, $2)",  "params": ["c-1042", 6200],  "connection_id": "8f14e45f-ceea-4e6f-8b23-1e2d3c4b5a6f"}'