Skip to content

YAML Views

Views are pre-approved, named SQL queries defined in YAML files. They let you publish a curated set of data access patterns — with typed parameters and descriptions — without exposing raw SQL to API consumers.

Use views to:

  • Give AI tools and dashboards clean, stable query interfaces
  • Enforce read patterns (consumers call a view name, not arbitrary SQL)
  • Document data access for compliance reviewers

Enabling views

Set TDB_VIEWS_DIR to a directory containing your view YAML files. Views are loaded at startup. If the variable is not set, the views feature is disabled.

TDB_VIEWS_DIR=/etc/tdb/views

View definition format

Each view is a separate .yml or .yaml file. One file = one view.

Minimal view (no parameters):

name: daily_signups
description: "New user signups grouped by day"
source: analytics_db
sql: |
  SELECT DATE(created_at) AS day, COUNT(*) AS signups
  FROM users
  GROUP BY day
  ORDER BY day DESC

Parameterised view:

name: orders_by_status
description: "Orders filtered by status and capped at a row limit"
source: orders_db
sql: "SELECT * FROM orders WHERE status = :status LIMIT :limit"
parameters:
  - name: status
    type: string
    description: "Order status (pending, shipped, delivered)"
    required: true
  - name: limit
    type: integer
    description: "Maximum number of rows to return"
    required: false
    default: 50

Fields

Field Required Description
name Yes View name (1–128 chars). Must be unique across all view files.
description No Human-readable description shown in API responses
source Yes Registered source name (not ID) to execute the query against
sql Yes SQL query template. Use :param_name placeholders for parameters.
parameters No List of parameter definitions (see below)

Parameter fields

Field Required Description
name Yes Parameter name (1–64 chars). Must match a :placeholder in the SQL.
type No One of string, integer, float, boolean. Default: string.
description No Human-readable description
required No If true, callers must supply a value. Default: false.
default No Default value used when the parameter is omitted. Ignored if required: true.

Parameter types and SQL safety

Parameter values are type-checked and safely embedded before execution. TDB does not use string interpolation — each type produces a validated SQL literal:

Type Input SQL output
string "O'Brien" 'O''Brien' (single quotes escaped)
string on MySQL / Snowflake "C:\\dir" 'C:\\dir' (backslashes escaped too, from 0.12.0)
integer "42" or 42 42
float "3.14" or 3.14 3.14
boolean "true", "1", "yes" 1
boolean "false", "0", "no" 0

Supplying a value that cannot be coerced to the declared type returns HTTP 400.

MySQL and Snowflake treat a backslash inside a string literal as an escape character, so on those sources backslashes are doubled as well as quotes and the value arrives unchanged. Before enterprise 0.12.0 only quotes were escaped, and a string parameter containing a backslash could end the literal early — upgrade if you run views with string parameters against either engine.


REST endpoints

All view endpoints require Bearer auth (any valid API key with read role or above).

List views

GET /v1/views

Returns all view definitions loaded from TDB_VIEWS_DIR.

curl -H "Authorization: Bearer <KEY>" http://localhost:8000/v1/views

Response:

[
  {
    "name": "daily_signups",
    "description": "New user signups grouped by day",
    "source": "analytics_db",
    "parameters": []
  },
  {
    "name": "orders_by_status",
    "description": "Orders filtered by status and capped at a row limit",
    "source": "orders_db",
    "parameters": [
      {"name": "status", "type": "string", "description": "Order status (pending, shipped, delivered)", "required": true, "default": null},
      {"name": "limit", "type": "integer", "description": "Maximum number of rows to return", "required": false, "default": 50}
    ]
  }
]

Get a view

GET /v1/views/{name}

Returns the definition for a single view. Returns HTTP 404 if not found.

Run a view

POST /v1/views/{name}/run

Request body:

{
  "parameters": {"status": "pending"},
  "limit": 100
}
Field Type Required Description
parameters object No Key-value pairs for named parameters.
limit integer No Max rows to return. Default: 1,000, or TDB_MAX_ROWS if lower. At most TDB_MAX_ROWS; a larger value is refused with 400 and audited limit_exceeds_max (from 0.16.0; before, it was a fixed 1–1000 whatever TDB_MAX_ROWS said).
page_key, cursor array of strings, string No From 0.31.0: read the view page by page.

Response:

{
  "view": "orders_by_status",
  "source": "orders_db",
  "columns": ["id", "customer_id", "status", "amount"],
  "rows": [
    {"id": 1, "customer_id": 42, "status": "pending", "amount": 99.99}
  ],
  "rows_returned": 1,
  "truncated": false,
  "executed_at": "2026-05-22T09:01:23.456789Z"
}
curl -X POST http://localhost:8000/v1/views/orders_by_status/run \
  -H "Authorization: Bearer <KEY>" \
  -H "Content-Type: application/json" \
  -d '{"parameters": {"status": "pending"}, "limit": 100}'

MCP tool

Views are also callable via the MCP list_views and run_view tools when using Claude Desktop, Cursor, or any MCP client.

list_views()               → returns all view names and descriptions
run_view(name, params)     → executes the named view

A key's allowed_tools scope applies to both: the MCP tools above, and — from enterprise 0.12.0 — the REST routes on this page (list_views for listing and reading views, run_view for running one). A key scoped to ["run_view"] can run views and cannot send its own SQL. See RBAC → Restricting tool access.


Error responses

Scenario HTTP status Detail
View not found 404 View 'name' not found.
Missing required parameter 400 Missing required parameters: ['status']
Unknown parameter supplied 400 Unknown parameters: ['typo']
Type coercion failure 400 Parameter 'limit' expects integer, got 'abc'
Source not registered 400 View 'name' references source 'x' which is not registered.
Source rejected the view's SQL 400 View failed: <error from the source>
Source failed while running it 500 View execution failed: <error from the source>

The first four 400s are caused by the caller — a missing, unknown or mistyped parameter. View failed is usually caused by the view definition: the SQL in the YAML was accepted at load time but rejected by the source when it ran (an unknown column, a syntax error, a value that does not convert), so it surfaces on whoever calls the view rather than on whoever wrote it. Before enterprise 0.20.3 this was a 500. A parser error in the sql: field is the common case — check the view against the parameter syntax (:name, not {{ name }}) and run the same SQL through POST /v1/query to see the source's own error message.


Example: full setup

# 1. Create view files
mkdir /etc/tdb/views

cat > /etc/tdb/views/revenue_by_month.yml << 'EOF'
name: revenue_by_month
description: "Monthly revenue totals, optionally filtered by year"
source: orders_db
sql: |
  SELECT strftime('%Y-%m', order_date) AS month, SUM(amount) AS total
  FROM orders
  WHERE (:year = 0 OR strftime('%Y', order_date) = :year)
  GROUP BY month
  ORDER BY month DESC
parameters:
  - name: year
    type: string
    description: "4-digit year to filter by, or '0' for all years"
    required: false
    default: "0"
EOF

# 2. Start TDB with the views directory
TDB_VIEWS_DIR=/etc/tdb/views tdb serve

# 3. List views
curl -H "Authorization: Bearer <KEY>" http://localhost:8000/v1/views

# 4. Run a view
curl -X POST http://localhost:8000/v1/views/revenue_by_month/run \
  -H "Authorization: Bearer <KEY>" \
  -H "Content-Type: application/json" \
  -d '{"parameters": {"year": "2026"}}'

Reload behavior

Views are loaded once at startup. To add, remove, or modify view files, restart TDB. There is no hot-reload — a restart ensures the audit log records which view definition was active at query time.