---
updatedAt: 2026-08-21T14:19:02.000Z
agentTools:
  projectIndex: https://docs.greynoise.io/llms.txt
---

# Validate GNQL Query

Report whether a GNQL query would be accepted, without running it.

This endpoint runs no query against the dataset and reports no search
usage, so it is safe to call on every keystroke while a user composes
a query. It also returns *every* reason a query was rejected, not just
the first, with a character position for syntax errors so a client can
place a caret on the offending token.

A rejected query is not an error: it is a `200` with `valid` set to
`false`. The HTTP status describes whether the query could be checked,
so a caller can distinguish "your query is wrong" from "we could not
tell you". A `4xx` or `5xx` therefore always means the request itself
was malformed, unauthorized, or could not be serviced.

Validation is gated on the same entitlement as search, and applies
your plan's data reach and field restrictions: a query that is valid
but would be rewritten before execution returns `valid: true` with
`is_query_adjusted: true` and the rewritten query in `adjusted_query`.


# OpenAPI definition

```json
{
  "openapi": "3.0.0",
  "info": {
    "description": "GreyNoise is a cybersecurity company that collects and analyzes Internet-wide scan and attack traffic. Use GreyNoise to contextualize existing alerts, filter false-positives, identify compromised devices, and track emerging threats.",
    "version": "3.0.0",
    "title": "GreyNoise API",
    "contact": {
      "email": "hello@greynoise.io"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://www.greynoise.io/terms"
    }
  },
  "tags": [
    {
      "name": "GNQL",
      "description": "Calls to interface with GNQL (GreyNoise Query Language)."
    }
  ],
  "security": [
    {
      "APIKeyHeaderAuth": []
    }
  ],
  "paths": {
    "/v3/gnql/validate": {
      "post": {
        "tags": [
          "GNQL"
        ],
        "summary": "Validate GNQL Query",
        "operationId": "gnqlV3Validate",
        "description": "Report whether a GNQL query would be accepted, without running it.\n\nThis endpoint runs no query against the dataset and reports no search\nusage, so it is safe to call on every keystroke while a user composes\na query. It also returns *every* reason a query was rejected, not just\nthe first, with a character position for syntax errors so a client can\nplace a caret on the offending token.\n\nA rejected query is not an error: it is a `200` with `valid` set to\n`false`. The HTTP status describes whether the query could be checked,\nso a caller can distinguish \"your query is wrong\" from \"we could not\ntell you\". A `4xx` or `5xx` therefore always means the request itself\nwas malformed, unauthorized, or could not be serviced.\n\nValidation is gated on the same entitlement as search, and applies\nyour plan's data reach and field restrictions: a query that is valid\nbut would be rewritten before execution returns `valid: true` with\n`is_query_adjusted: true` and the rewritten query in `adjusted_query`.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "query"
                ],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "The GNQL query to validate. Leading and trailing\nwhitespace is trimmed; an empty query is a `400`.\n",
                    "maxLength": 4096,
                    "example": "classification:malicious last_seen:1d"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The query was checked. `valid` carries the verdict — a rejected\nquery is reported here, not as a `4xx`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GNQLValidateResponse"
                },
                "examples": {
                  "valid": {
                    "summary": "Query is accepted as submitted",
                    "value": {
                      "valid": true,
                      "query": "classification:malicious last_seen:1d"
                    }
                  },
                  "adjusted": {
                    "summary": "Query is valid but plan limitations rewrite it",
                    "value": {
                      "valid": true,
                      "query": "classification:malicious last_seen:90d",
                      "adjusted_query": "classification:malicious last_seen:7d",
                      "is_query_adjusted": true,
                      "message": "The requested data reach of 90 days exceeds your plan; 7 days was used."
                    }
                  },
                  "invalid": {
                    "summary": "Query is rejected, with every reason listed",
                    "value": {
                      "valid": false,
                      "query": "(classification:malicious AND",
                      "message": "Expected a search term after 'AND' but found end of query; Expected ')' but found end of query",
                      "errors": [
                        {
                          "message": "Expected a search term after 'AND' but found end of query",
                          "position": 29,
                          "expected": "search term",
                          "found": "end of query",
                          "code": "SYNTAX_ERROR"
                        },
                        {
                          "message": "Expected ')' but found end of query",
                          "position": 29,
                          "expected": "')'",
                          "found": "end of query",
                          "code": "SYNTAX_ERROR"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request - the body was not valid JSON, `query` was missing or\nempty, or the query exceeded 4096 characters.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorMessage"
                },
                "example": {
                  "message": "query is required"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "description": "Request entity too large - the request body exceeded 64 KiB.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorMessage"
                },
                "example": {
                  "message": "request body is too large"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/UnexpectedError"
          }
        }
      }
    }
  },
  "servers": [
    {
      "url": "https://api.greynoise.io"
    },
    {
      "url": "http://api.greynoise.io"
    }
  ],
  "components": {
    "securitySchemes": {
      "APIKeyHeaderAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "key"
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Unauthorized. Please check your API key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorMessage"
            },
            "example": {
              "message": "Unauthorized"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Forbidden - request is not authorized due to an invalid API key or plan limitations.\nIf due to plan limitations, contact sales@greynoise.io to upgrade your plan and unlock full results.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorMessage"
            },
            "example": {
              "message": "Forbidden"
            }
          }
        }
      },
      "UnexpectedError": {
        "description": "Unexpected error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorMessage"
            },
            "example": {
              "message": "Encountered error while performing request"
            }
          }
        }
      }
    },
    "schemas": {
      "ErrorMessage": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string"
          }
        },
        "required": [
          "message"
        ]
      },
      "GNQLValidateResponse": {
        "type": "object",
        "properties": {
          "valid": {
            "type": "boolean",
            "description": "Whether the query would be accepted. `false` is a verdict, not an\nerror - it is returned with HTTP 200.\n",
            "example": true
          },
          "query": {
            "type": "string",
            "description": "The query that was validated, after whitespace trimming",
            "example": "classification:malicious last_seen:1d"
          },
          "errors": {
            "type": "array",
            "description": "Every reason the query was rejected, not only the first. Absent\nwhen `valid` is `true`.\n",
            "items": {
              "$ref": "#/components/schemas/GNQLQueryError"
            }
          },
          "message": {
            "type": "string",
            "description": "The rejection reason when `valid` is `false`, or a data-reach or\nfield-restriction notice when it is `true`.\n",
            "example": "invalid query"
          },
          "adjusted_query": {
            "type": "string",
            "description": "The query that would actually be executed, when plan limitations\nwould rewrite the submitted query. Absent when the query would run\nunchanged.\n",
            "example": "classification:malicious last_seen:7d"
          },
          "is_query_adjusted": {
            "type": "boolean",
            "description": "Whether `adjusted_query` differs from the submitted query, so a\nclient can show what will actually run.\n",
            "example": false
          }
        },
        "required": [
          "valid",
          "query"
        ]
      },
      "GNQLQueryError": {
        "type": "object",
        "description": "One machine-readable reason a query was rejected. This mirrors a\n`details[]` entry on a search `400`, so a client can render both the\nsame way.\n",
        "properties": {
          "message": {
            "type": "string",
            "description": "Human-readable description of the problem",
            "example": "unexpected end of query"
          },
          "position": {
            "type": "integer",
            "description": "Zero-based character offset in the query the error points at.\nAbsent when the error is not tied to a position. Note that `0` is\na real offset - a query beginning with an operator fails at\noffset 0 - so distinguish absent from zero.\n",
            "example": 26
          },
          "expected": {
            "type": "string",
            "description": "What the parser expected at `position`",
            "example": "a search term"
          },
          "found": {
            "type": "string",
            "description": "What the parser found at `position` instead",
            "example": "<end of input>"
          },
          "code": {
            "type": "string",
            "description": "Machine-readable error code. Current values include\n`SYNTAX_ERROR`, `FIELD_NOT_FOUND`, `UNSUPPORTED_FIELD`,\n`DISCONTINUED_FIELD`, `INVALID_VALUE_TYPE`, `UNSUPPORTED_KEYWORD`,\n`INVALID_TIME_INTERVAL`, `INVALID_DATE_FORMAT`,\n`QUERY_TOO_COMPLEX`, `UNSUPPORTED_BARE_TERM`,\n`RECENCY_REQUIRES_VALUE`, `UNSUPPORTED_DATE_RESOLUTION` and\n`INVALID_QUERY`. New codes may be added, so treat an unrecognized\ncode as a generic rejection and fall back to `message`.\n",
            "example": "SYNTAX_ERROR"
          },
          "field": {
            "type": "string",
            "description": "The GNQL field the error concerns, when it is field-specific",
            "example": "classification"
          }
        },
        "required": [
          "message"
        ]
      }
    }
  }
}
```