Skip to main content
When built-in metrics don’t cover your question, you can run SQL directly against ClickHouse. Tell scopes every query to your workspace and only allows SELECT — so you get full query power without risk of modifying data.

Quick start

From the CLI:
tell query "SELECT event_name, COUNT(*) as total FROM events_v1 GROUP BY event_name ORDER BY total DESC"
Or via the API:
curl -X POST "https://your-tell-server/api/v1/data/query" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Workspace-ID: 1" \
  -d '{"query": "SELECT event_name, COUNT(*) as total FROM events_v1 GROUP BY event_name ORDER BY total DESC"}'
You don’t need to worry about workspace isolation — Tell rewrites your query to scope it automatically.

Available tables

TableContents
events_v1Product events — timestamp, event_name, device_id, session_id, properties (JSON)
context_v1Device and session context — device_type, operating_system, country, app_version, and more
logs_v1Application logs — level, source, service, message (JSON)
users_v1User profiles — user_id, email, name
user_traits_v1User properties — user_id, trait_key, trait_value
user_devicesDevice-to-user mappings — user_id, device_id, linked_at
snapshots_v1Connector snapshots — connector, entity, metrics (JSON)
To discover tables and columns programmatically:
# List available tables
curl "https://your-tell-server/api/v1/data/sources" \
  -H "Authorization: Bearer $TOKEN" -H "X-Workspace-ID: 1"

# List columns for a table
curl "https://your-tell-server/api/v1/data/sources/events/fields" \
  -H "Authorization: Bearer $TOKEN" -H "X-Workspace-ID: 1"

# Get distinct values for a field
curl "https://your-tell-server/api/v1/data/sources/events/values/event_name" \
  -H "Authorization: Bearer $TOKEN" -H "X-Workspace-ID: 1"

Example queries

Events by type in the last 7 days:
SELECT event_name, COUNT(*) as count, COUNT(DISTINCT device_id) as unique_devices
FROM events_v1
WHERE timestamp > now() - INTERVAL 7 DAY
GROUP BY event_name
ORDER BY count DESC
Error logs by service in the last 24 hours:
SELECT service, COUNT(*) as errors
FROM logs_v1
WHERE level IN ('ERROR', 'CRITICAL')
  AND timestamp > now() - INTERVAL 24 HOUR
GROUP BY service
ORDER BY errors DESC
Users with multiple devices:
SELECT u.user_id, u.email, COUNT(DISTINCT ud.device_id) as devices
FROM users_v1 u
JOIN user_devices ud ON u.user_id = ud.user_id
GROUP BY u.user_id, u.email
HAVING devices > 1
ORDER BY devices DESC

CLI output formats

The tell query command supports three output formats:
# Table (default) — columns aligned in your terminal
tell query "SELECT event_name, COUNT(*) FROM events_v1 GROUP BY event_name"

# JSON — one object per row
tell query --format json "SELECT event_name, COUNT(*) FROM events_v1 GROUP BY event_name"

# CSV — for piping into other tools
tell query --format csv "SELECT event_name, COUNT(*) FROM events_v1 GROUP BY event_name"

Security

Tell validates every query before execution:
  • SELECT onlyINSERT, UPDATE, DELETE, DROP, ALTER, and TRUNCATE are blocked
  • Workspace scoping — table references are rewritten to {workspace_id}.table_name automatically
  • No multi-statement — semicolons cannot separate multiple queries
  • Rate limited — 30 queries per minute via the API
CTEs (WITH ... AS), subqueries, and JOINs all work normally.

Limits

ParameterDefaultMaximum
Result rows1,00010,000
Execution time60s60s
API rate30 req/min
Set a custom limit with LIMIT in your query or "limit" in the API request body.