Troubleshoot Analytics
Most problems appear when you save a query, not when you run it. Knowing which of the two happened rules out half the possible causes immediately.
Start with three checks
- Confirm the query saved at all — a query that will not save has a validation problem, not a data or access problem.
- Confirm the app owns what the query names: its data tables for an internal query, its connection secret for an external one.
- Confirm you supplied a value for every caller placeholder. There are no optional placeholders.
A query will not save
These are validation failures, returned as 400. They describe the query
text or its configuration and never depend on your data. The specific message
is under errors.query_text, errors.secret_key, or errors.description in
the response, and a suggested fix, when there is one, is in detail.
Statement and table problems
| Message | What it means | Fix |
|---|---|---|
Unknown table(s) for app '<slug>': <names> | An internal query names a table that is not a data table of this app | Check the name in Datatables. A table in a different app reads as unknown here, not as a permission error |
The query's tables could not be resolved: … | Analytics could not work out which tables an internal query reads, so it refused the query rather than allow it | Simplify the statement, for example by moving a nested query into a WITH clause |
Only a single statement is allowed in analytics queries. | The query text contains more than one statement | Send one statement |
Only SELECT queries are allowed in analytics. … | The statement is not a read, or contains a write such as INSERT, UPDATE, DELETE, MERGE, or SELECT ... INTO anywhere inside it | Send one read statement |
Function '<name>' is not allowed in analytics queries. | The query calls a blocked function | Remove the function call |
Cross-schema access is not allowed for internal analytics queries … | An internal query names a schema other than the site's own | Remove the schema name and use the table's logical name |
Invalid SQL syntax: … or Query text could not be read as <dialect> SQL. | The statement cannot be read in the connector's SQL dialect | Correct the syntax for that database |
Placeholder problems
| Message | What it means | Fix |
|---|---|---|
Query placeholder {{ x }} is inside quotation marks. | The placeholder is only part of a quoted text, such as '%{{ x }}%' | Build the text in SQL, for example concat('%', {{ x }}, '%'). A placeholder that is the whole quoted text, '{{ x }}', is accepted |
Query placeholder {{ x }} is written as a quoted identifier. or A placeholder stands in for a value, not for a table name. | The placeholder is used as a column or table name | Name the column or table in the query text |
Query placeholder {{ x }} is immediately followed by a '::' cast. | A :: cast is written directly after the placeholder | Write cast({{ x }} as int), or put a space before :: |
Query placeholder {{ … }} is not a plain value reference. | The placeholder contains a filter or expression, such as {{ x | default('open') }} | Name one value and do any transformation in SQL |
Query text contains a template control block … | The text contains {% … %} or {# … #} | Remove the block; write a separate query for each variant |
Query placeholder {{ secret }} refers to the secret namespace as a whole. | The placeholder names the secret store instead of one secret | Name one secret, such as {{ secret.region_filter }} |
Secret placeholders are only allowed in WHERE, JOIN ... ON and HAVING conditions. | A stored secret sits in the returned columns, an ORDER BY, or another position that is not a filter | Move it into a filter. The built-in user profile is not limited this way |
Connection and secret problems
| Message | What it means | Fix |
|---|---|---|
Secret key is required for external database connections. | The query is external but names no connection secret | Name a secret, or switch the query to internal. External is the default, so a query declaring no mode lands here |
Secret '<key>' was not found for this tenant | The connection secret does not exist for this site and app | Create the secret, or check whether it was stored against a different app |
Secret type '<slug>' is not a supported analytics … | The connection secret exists but its type sits outside the analytics set | Create a secret of the matching analytics type — the type selects the connector |
Referenced secrets not found: <names> | A secret. placeholder in the query text names a secret that does not exist for this site and app | Create the secret, or correct the name |
Description must be at most 1000 characters. | The description is longer than 1,000 characters | Shorten the description |
Elasticsearch body problems
| Message | What it means | Fix |
|---|---|---|
Elasticsearch query_text must be valid JSON | The body is not valid JSON, which includes a placeholder written outside a JSON string | Check for a trailing comma or an unquoted key, and put every placeholder inside quotation marks |
'<key>' is not allowed in analytics queries for security reasons. | The body contains a script or runtime_mappings key at some depth | Remove the key |
A placeholder cannot be used as a key: … | A placeholder is used as a JSON key | Write the key in the body and use the placeholder only as a value |
An edit to an older query is refused
Every edit re-runs all save checks, even an edit that only renames the query
or changes its tags. A query saved before a check existed is refused on its
next edit if its text breaks that check — for example, a | default(...)
filter, a placeholder that is only part of a quoted text, or a stored secret
in the returned columns. Fix the query text in the same edit, using the tables
above.
A placeholder problem in an older query also stops it from running, because each run reads the placeholders with the current rules. The run fails with the same message listed under Placeholder problems.
A query saves but fails to run
These depend on values, data, or connectivity, so they only appear at
execution. They come back as HTTP 400 with code BAD_REQUEST. The cause is
in message, and a suggested fix, when there is one, is in detail.
| Message | What it means | Fix |
|---|---|---|
Query placeholder {{ x }} has no value. | The run did not supply a value for a caller placeholder | Send every placeholder's value in params |
Query placeholder {{ x }} was given an empty list. | A list parameter had no items | Send at least one item, or skip the run |
Secret '<name>' referenced by query '<slug>' was not found | A secret. placeholder names a secret that was deleted after the query was saved | Recreate the secret, or edit the query to stop referencing it |
Do not pass secret.* in params; … | A parameter name starts with secret. | Remove it; secrets are read from storage, not sent by callers |
secret.user_profile requires an authenticated user. | The query uses the built-in profile but the caller is not signed in | Run the query as an authenticated user |
Query placeholder {{ secret.x.y }} refers to 'y', which does not exist in that value. | The secret's stored JSON has no such field | Correct the field name or the secret's value |
Query returned … rows | The result exceeded the connection's row maximum | Add a row limit — LIMIT for SQL and internal queries, size for Elasticsearch — or aggregate instead of returning individual rows. See Bound every query |
| A type error from the database naming a value you supplied | The value's type does not fit where the placeholder sits, for example text compared with a number column. The wording comes from the database itself, so it varies by connector | Send the value in the type the column expects, or cast the placeholder in SQL |
| A missing-relation error for a table that does exist | The data table was renamed or removed after the query was saved, so its logical name no longer resolves | Update the query to the table's current name |
| A connection failure reported by the database | The database refused the connection or was unreachable. It returns 400, like a query error, so tell the two apart by the message. For an internal query the message starts Internal DB query failed: | For an external database, confirm the host, port, and credential are still valid; for an internal query, retry and report it if it persists. Analytics applies best-effort redaction, but the returned provider message is not a complete sanitization boundary; review it before sharing |
Results are not what you expect
Nothing failed here — the query ran and returned results that are not what you expected.
| Symptom | Why | What to do |
|---|---|---|
| Aggregation rows come back instead of documents | An Elasticsearch query containing aggregations returns the aggregation output | Remove the aggregations to retrieve documents. Each row identifies the aggregation it came from |
| A per-user query returns nothing | The query filters on the built-in caller profile, which returns only that caller's rows | Confirm the request is authenticated, and that the compared value matches how the data actually stores it |
| The result count looks wrong for a search | For an Elasticsearch search, the reported total counts matching documents | Read it as documents matched, which is not necessarily the number of rows returned |
Access is refused
Running a query has two 403 refusal paths, and the message tells you which
check stopped the caller. A member of the organization that owns the site is
allowed without these checks; any other user must pass both. These checks
apply only to running a query; see
who can manage a query.
No app roles assigned means a site user holds no role at all on the app
that owns the query. It is checked on every run, before the query is read.
You do not have permission to execute query '<slug>' means the caller
does hold a role on the app, but someone has since edited this specific
query's policy to name which roles may run it, and the caller's role is not
among them. No query starts out restricted this way, so this message means
someone deliberately locked this one down — check its policy rather than
assuming a general fault.
See Who can run and manage queries for both checks and how to set the second one.
Analytics is unavailable for the site
A 402 with a message about the account not being active means the
organization's account is suspended. Every analytics request is refused while
that lasts — running a query and managing query definitions alike — so this is
a billing question rather than a query problem. Contact support or update the
billing details, as the message says.
Check SDK behavior
- These examples use JavaScript SDK 1.5.3 and Python SDK 0.2.1.
- The two SDKs take parameters differently. JavaScript wraps them in an
options object,
analytics.execute('SLUG', {params: {...}}); Python takes them directly,client.analytics.execute("SLUG", params={...}). Check this first if you adapted a call from the other language's tab. - Both SDKs cover execution only. Create, edit, save, and delete queries in TaruviBase Console.
- Neither SDK unwraps the response for you — read the rows from
dataon the returned object rather than expecting the rows directly. - Compare an SDK call with a Run from the query's page in Console. Console counts the rows it received and doesn't show the execution key, so expect those two differences; see the execution response format.
Prepare a support request
Include:
- the execution key returned with the failed run;
- the query slug and the app that owns it;
- the connection mode, and the connection secret key for an external query;
- the parameter names you supplied, without their values if any are sensitive; and
- the error category and a summary of the message with any sensitive details removed.
Keep the report safe by leaving out credentials, parameter values, personal data, and infrastructure details. Raw caller parameters and final error text are persisted with the execution.