# Error handling

## Errors

| Error | `errorCode` | HTTP | Meaning |
| --- | --- | --- | --- |
| `SQL_ERROR_INVALID_QUERY` | `INVALID_ARGUMENT` | 400 | The query was rejected, for example for bad syntax, an unknown table or column, a disallowed function, or a missing `dataset_rid` filter. `parameters.detail` says why. |
| `SQL_ERROR_RESULT_SIZE_EXCEEDED` | `INVALID_ARGUMENT` | 400 | The result exceeded the 1 GB result size limit. Add a `LIMIT`, narrow the filters, or use export. |
| `SQL_ERROR_QUERY_TIMEOUT` | `INVALID_ARGUMENT` | 400 | The query timed out before completing. Add time bounds, channel filters, or tag filters to reduce the data scanned. |
| `SQL_ERROR_EXPORT_NOT_SUPPORTED_FOR_BACKEND` | `INVALID_ARGUMENT` | 400 | Export was called on a query that reads only the system tables provided for Postgres clients, such as `information_schema.tables`. |
| `SQL_ERROR_UNAUTHENTICATED` | `UNAUTHORIZED` | 401 | The request could not be authenticated. A missing or invalid API key returns a 401 without this error in the body. |
| `SQL_ERROR_PERMISSION_DENIED` | `PERMISSION_DENIED` | 403 | You don’t have view access to the workspace named in `workspace_rid`. |
| `SQL_ERROR_DATASETS_NOT_FOUND` | `NOT_FOUND` | 404 | One or more requested datasets don’t exist in the given workspace, or you can’t read them. |
| `SQL_ERROR_DATA_NOT_FOUND_FOR_TYPE` | `NOT_FOUND` | 404 | No data was found for one or more of the referenced datasets. |
| `SQL_ERROR_RESOURCE_EXHAUSTED` |  | 429 | The query exceeded a resource limit: the organization’s request rate, a memory limit, or the service’s capacity at that moment. |
| `SQL_ERROR_EXECUTION_FAILED` | `INTERNAL` | 500 | An unexpected internal error occurred during execution. |
| `SQL_ERROR_QUERY_SCOPE_TOO_LARGE` | `INTERNAL` | 500 | The query spans more datasets than can be planned at once. Add a `dataset_rid` or tag filter to narrow the datasets, or split the query. |
| `SQL_ERROR_QUERY_CANCELLED` | `INTERNAL` | 500 | The query was cancelled before it completed. |
| `SQL_ERROR_EXPORT_NOT_CONFIGURED` | `FAILED_PRECONDITION` | 500 | Export is not configured for this deployment. |

Requests made with the same API key are also throttled before they reach the SQL service. Going over returns 429 with a plain-text body and a `Retry-After` header.

## Request validation happens first

A field that violates its constraints is rejected before the query reaches the planner: a missing `workspace_rid`, a query over 100,000 characters, or `parquet_options` on a CSV export. Those failures carry `INVALID_ARGUMENT` with the name `ValidationNominalIo:ConstraintViolation` and an empty `parameters` map.

That is a different failure from `SQL_ERROR_INVALID_QUERY`, which means your SQL was parsed and then rejected. The distinction tells you where to look: the request body, or the query text.

## Error response schema

Most errors serialize as a JSON object with the code, a name identifying what failed, a correlation id, and a `parameters` map. Querying a column that doesn’t exist returns:

```json
{
  "errorCode": "INVALID_ARGUMENT",
  "errorName": "NominalSqlV1:SqlErrorInvalidQuery",
  "errorInstanceId": "942263b0-a664-4f4b-a249-066b02f20371",
  "parameters": {
    "detail": "From line 1, column 8 to line 1, column 11: Column 'nope' not found in any table",
    "sqlQueryId": "84d7919a-dae5-499f-a938-0d0bcf25c81b"
  }
}
```

Three details are worth knowing when you’re debugging:

- **Read `parameters.detail` first.** On validation failures it carries the specific reason, with line and column positions into your query. `errorName` only tells you the category.
- **`errorName` tells you which layer rejected the request.** `NominalSqlV1:SqlErrorInvalidQuery` and its siblings come from the SQL layer, and carry a `sqlQueryId` you can quote in a bug report. A bare `Default:InvalidArgument` with an empty `parameters` map means the request body never parsed at all.
- **`RESOURCE_EXHAUSTED` is shaped differently.** It returns HTTP 429 with a body containing only `parameters`, and no `errorCode` or `errorName`, because there is no equivalent code in the serialization format the other errors use. Don’t parse it the same way.

## Next steps

- [Query](https://dev.nominal.io/sql-reference/rest-api/query.md): run a bounded query.
- [Writing queries](https://dev.nominal.io/sql-guides/writing-queries.md): query patterns and examples.
